Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e77590a125 | |||
| ca330b2d80 | |||
| 20f1524701 | |||
| 96b2a15643 | |||
| ad59392e02 | |||
| 29f92b887c | |||
| 46328211c2 | |||
| 3760130d1f | |||
| 1094827fac | |||
| 36fcb44af5 | |||
| a683fbac4b | |||
| 64ad9019d1 | |||
| 1e80d5c8a8 | |||
| ac765b1ee8 | |||
| 850c4f0a9a | |||
| 4c32999752 | |||
| 76823b167a | |||
| 9994cfd260 | |||
| 1750236474 | |||
| fed6e31f9e | |||
| 9bafc7d4a1 | |||
| be58b48d20 | |||
| 303abac980 | |||
| d1a459efc2 | |||
| 96069e6f09 | |||
| 301a721329 | |||
| e8c6801a32 | |||
| 355f3ef614 | |||
| eaa6083586 | |||
| a0c5d02c51 | |||
| 76f9f5fc82 | |||
| 8e2598e93b | |||
| c0672c41df | |||
| 4c6601fbc5 | |||
| b99c87d1bd | |||
| 66ffbfe951 | |||
| 8cb01a1027 | |||
| 193e5eccbb | |||
| bf7fc28bb0 | |||
| e35cbc343a | |||
| 1c7b053c8c | |||
| 88cdcd4b47 | |||
| 1432fed8f9 | |||
| a14307b9a4 | |||
| 3bdce5aa08 | |||
| d7526f93b8 | |||
| 8932578b90 | |||
| 631ac9881d | |||
| 78334ed797 | |||
| b06da89654 | |||
| d6c2e78993 | |||
| 67d1ac551d | |||
| dcb6be717d | |||
| f2c04eec1a | |||
| a08311cf43 | |||
| 1e28f3aa86 | |||
| 426af41f2c | |||
| fd08a219f9 | |||
| 25b6aec0e9 | |||
| 3e40209d15 | |||
| 54ff94f905 | |||
| 7a4faf6b0d | |||
| 8922b00a5f | |||
| 702efa8693 | |||
| e7a8c897a3 | |||
| c59d1d87bd | |||
| a706efb8b3 | |||
| e391e83a00 | |||
| 2940bf20d8 | |||
| 081db5a58b | |||
| b34805272f | |||
| 98443dba99 | |||
| dd6e733d8c | |||
| 92d3439c3b | |||
| bd155cd600 | |||
| e14e53c0ae | |||
| 77a6a63174 | |||
| 40f2463f09 | |||
| e5d1048c8d | |||
| bf3b529fb2 | |||
| d0fde46027 | |||
| 0f348dff10 | |||
| d3f5080e73 | |||
| a453c8ec1e | |||
| 2a60aa8f59 | |||
| 41e7870509 | |||
| 57f66b77ab | |||
| 53572406e9 | |||
| a288255ab1 | |||
| 353c958ae0 | |||
| bc5ab0d7e5 | |||
| 9d915b5640 | |||
| 3aaee270eb | |||
| d53a7156e2 |
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
liberapay: dennis.guse
|
||||||
|
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report issues of OpenTracks features
|
||||||
|
title: ''
|
||||||
|
labels: potential bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
If applicable:
|
||||||
|
* add screenshots
|
||||||
|
* add logcat output
|
||||||
|
|
||||||
|
**Technical information**
|
||||||
|
- Device: [e.g. Nexus 7 (2013)]
|
||||||
|
- OS: [e.g. LineageOS 15.1]
|
||||||
|
- Version [e.g. v3.2.4]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for OpenTracks
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Thanks for your contribution.
|
||||||
|
|
||||||
|
**Describe the pull request**
|
||||||
|
A clear and concise description of what the pull request changes/adds.
|
||||||
|
|
||||||
|
**Link to the the issue**
|
||||||
|
(If available): The link to the issue that this pull request solves.
|
||||||
|
|
||||||
|
**License agreement**
|
||||||
|
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
|
||||||
@@ -73,8 +73,12 @@ captures/
|
|||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
# Uncomment the following line if you do not want to check your keystore files in.
|
*.jks
|
||||||
#*.jks
|
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Fastlane
|
||||||
|
Appfile
|
||||||
|
releasePlayStore/*
|
||||||
|
Gemfile.lock
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RenderSettings">
|
||||||
|
<option name="showDecorations" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -19,25 +19,26 @@ android:
|
|||||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||||
- tools
|
- tools
|
||||||
- build-tools-28.0.3
|
- build-tools-28.0.3
|
||||||
- android-28
|
- android-29
|
||||||
|
|
||||||
# Emulator Management: Create, Start and Wait
|
# Emulator Management: Create, Start and Wait
|
||||||
install:
|
#install:
|
||||||
- echo y | sdkmanager "platform-tools"
|
# - echo y | sdkmanager "platform-tools"
|
||||||
- echo y | sdkmanager --channel=4 "emulator"
|
# - echo y | sdkmanager --channel=4 "emulator"
|
||||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
# - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
# - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
# - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||||
- android-wait-for-emulator
|
# - android-wait-for-emulator
|
||||||
- adb shell input keyevent 82 &
|
# - adb shell input keyevent 82 &
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew clean build
|
- ./gradlew clean build
|
||||||
- ./gradlew connectedCheck
|
# - ./gradlew connectedCheck
|
||||||
|
|
||||||
sbefore_cache:
|
sbefore_cache:
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.gradle/caches/
|
- $HOME/.gradle/caches/
|
||||||
|
|||||||
@@ -1,12 +1,36 @@
|
|||||||
# OpenTrack: Developer information
|
# OpenTracks: Developer information
|
||||||
|
|
||||||
## Create releases for FDroid
|
## Craft new releases
|
||||||
|
|
||||||
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
||||||
2. Decide on _version name_ (semantic versioning)
|
2. Decide on _version name_ (semantic versioning)
|
||||||
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
|
3. Manually update _version code_ and _version name_ in `AndroidManifest.xml` (used by FDroid)
|
||||||
4. Create changelog (`_version code_.txt`)
|
4. Create changelog (`_version code_.txt`)
|
||||||
5. Create commit with all changes
|
5. Create commit with all changes
|
||||||
6. Push commits
|
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||||
7. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
7. Push commits and tags to public repository
|
||||||
8. Push the tagged commit to public repository
|
8. Add changelog to tag (Github releases)
|
||||||
|
|
||||||
|
## F-Droid
|
||||||
|
Application id: de.dennisguse.opentracks
|
||||||
|
|
||||||
|
It if it properly tagged F-Droid takes care of building the app and publishing.
|
||||||
|
Usually, it takes two days before the update is published (build, signed, and published).
|
||||||
|
|
||||||
|
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
|
||||||
|
|
||||||
|
## Google Play Store
|
||||||
|
Application id: de.dennisguse.opentracks.playstore
|
||||||
|
|
||||||
|
Requirement: `gem install fastlane`
|
||||||
|
|
||||||
|
1. Update store meta data (e.g., description and screenshots)
|
||||||
|
`fastlane supply --version_code $(git rev-list HEAD --count master) --skip_upload_apk`
|
||||||
|
(requires API key)
|
||||||
|
2. Build signed apk
|
||||||
|
(requires keystore)
|
||||||
|
3. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
||||||
|
|
||||||
|
## Translations (localization)
|
||||||
|
|
||||||
|
Translations of the OpenTracks are handled in Transifex: [https://www.transifex.com/opentracks](https://www.transifex.com/opentracks/).
|
||||||
@@ -2,9 +2,30 @@
|
|||||||
|
|
||||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||||
|
|
||||||
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
<table>
|
||||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="60">
|
<tr>
|
||||||
</a>
|
<th>Free</th>
|
||||||
|
<th>Paid</th>
|
||||||
|
<th>Donations</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
||||||
|
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
|
||||||
|
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://liberapay.com/dennis.guse/donate">
|
||||||
|
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
* __Tracking:__ track your sport and outdoor activities
|
* __Tracking:__ track your sport and outdoor activities
|
||||||
@@ -26,9 +47,14 @@ _Please note:_
|
|||||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
||||||
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
||||||
|
|
||||||
<a href="https://liberapay.com/dennis.guse/donate">
|
|
||||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
|
## Screenshots
|
||||||
</a>
|
<div>
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||||
|
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +35,14 @@ def getVersionName = { ->
|
|||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'describe', '--tags', '--dirty'
|
commandLine 'git', 'describe', '--tags'
|
||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return stdout.toString().trim()
|
exec {
|
||||||
|
commandLine 'git', 'log', '--format="%h"', '-n 1'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().replace('\n', '-').replace('"', '').trim()
|
||||||
}
|
}
|
||||||
catch (ignored) {
|
catch (ignored) {
|
||||||
return null
|
return null
|
||||||
@@ -70,15 +74,16 @@ android {
|
|||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
releasePlayStore {
|
||||||
|
applicationIdSuffix ".playstore"
|
||||||
|
versionNameSuffix "-PlayStore"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.resValue "string", "applicationId", variant.applicationId
|
variant.resValue "string", "applicationId", variant.applicationId
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
disable "AppCompatResource" // TODO Disable complaint to use androidx's SearchView as it is null.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -43,7 +43,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -41,7 +41,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -33,7 +33,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -39,7 +39,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -18,7 +18,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -21,7 +21,7 @@
|
|||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work rdf:about="">
|
<cc:Work rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:title></dc:title>
|
<dc:title />
|
||||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
# This file contains the fastlane.tools configuration
|
||||||
|
# You can find the documentation at https://docs.fastlane.tools
|
||||||
|
#
|
||||||
|
# For a list of all available actions, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/actions
|
||||||
|
#
|
||||||
|
# For a list of all available plugins, check out
|
||||||
|
#
|
||||||
|
# https://docs.fastlane.tools/plugins/available-plugins
|
||||||
|
#
|
||||||
|
|
||||||
|
# Uncomment the line if you want fastlane to automatically update itself
|
||||||
|
# update_fastlane
|
||||||
|
|
||||||
|
default_platform(:android)
|
||||||
|
|
||||||
|
platform :android do
|
||||||
|
desc "Runs all the tests"
|
||||||
|
lane :test do
|
||||||
|
gradle(task: "test")
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Submit a new Beta Build to Crashlytics Beta"
|
||||||
|
lane :beta do
|
||||||
|
gradle(task: "clean assembleRelease")
|
||||||
|
crashlytics
|
||||||
|
|
||||||
|
# sh "your_script.sh"
|
||||||
|
# You can also use other beta testing services here
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Deploy a new version to the Google Play"
|
||||||
|
lane :deploy do
|
||||||
|
gradle(task: "clean assembleRelease")
|
||||||
|
upload_to_play_store
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
||||||
|
|
||||||
Funktionen & Features:
|
Funktionen & Features:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Open Tracks – ein GPS-Tracker, der deine Daten für sich behält.
|
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
v3.2.4: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Settings activity crashes
|
||||||
|
Removed possibility to change voice/split frequency from the context menu
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v3.2.5: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Disable activity change
|
||||||
|
- Add permission for Android 10: recording location data
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix reset settings
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Preferences
|
||||||
|
- if Settings cannot be loaded reset all preferences
|
||||||
|
- moved access to preferences to PreferencesUtil
|
||||||
|
- moved default of preferences from code to xml
|
||||||
|
- use SummaryProvider
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v3.2.6: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Heart rate monitor: scan for available devices
|
||||||
|
- Remove shortcut to Bluetooth settings
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Preferences: moved default of preferences from code to xml
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.7: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Notification show GPS accuracy
|
||||||
|
- Show heads-up notification if GPS accuracy drops beyond threshold
|
||||||
|
- Always show chart by time as well as by distance
|
||||||
|
- Added help (in Settings)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- TrackDetailActivity: disabled non-working landscape mode (temporary)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.8: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Chart: zoom via gestures
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Export via KMZ did not export picture filenames correctly
|
||||||
|
- Sharing multiple tracks did not include KMZ files
|
||||||
|
- MarkerDetailActivity's UI was not shown
|
||||||
|
- Settings crashed if preference of heart rate was not initialized
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v3.2.9: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Option to keep screen on while recording
|
||||||
|
- Markers: removed split markers completely
|
||||||
|
- Voice announcement now work without READ_PHONE_STATE permission
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Statistics: heart rate was not shown
|
||||||
|
- Statistics: time was briefly reset to 0s
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.2.10: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- ChartView is not configurable anymore
|
||||||
|
- After reboot: removed automatic restart of an unfinished recording
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Speech announcements use MAY_DUCK (again)
|
||||||
|
- ChartView shows markers (again)
|
||||||
|
- Fixed landscape layout of TrackListActivity
|
||||||
|
- Request legacy file API behavior (necessary for Android 10)
|
||||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
OpenTracks es un tracker 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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||||
|
|
||||||
|
Fonctionnalités :
|
||||||
|
|
||||||
|
* Suivi d'activité : enregistre vos activités sportives et de plein air
|
||||||
|
* Photos et repères : ajout de photos et de repères pendant l'enregistrement de l'activité
|
||||||
|
* Rythme cardiaque : support des capteurs Bluetooth LE
|
||||||
|
* Export : export des traces au format KML ou GPX
|
||||||
|
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
OpenTracks est une application de suivi d'activités sportives qui respecte totalement votre vie privée.
|
||||||
|
After Width: | Height: | Size: 125 KiB |
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
|
||||||
import android.app.ActivityManager.RunningServiceInfo;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the BootReceiver.
|
|
||||||
*
|
|
||||||
* @author Youtao Liu
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class BootReceiverTest {
|
|
||||||
private static final String SERVICE_NAME = "de.dennisguse.opentracks.services.TrackRecordingService";
|
|
||||||
|
|
||||||
private Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the behavior when receive notification which is the phone boot.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOnReceive_startService() {
|
|
||||||
// Make sure no TrackRecordingService
|
|
||||||
Intent stopIntent = new Intent(context, TrackRecordingService.class);
|
|
||||||
context.stopService(stopIntent);
|
|
||||||
Assert.assertFalse(isServiceExisted(context));
|
|
||||||
|
|
||||||
BootReceiver bootReceiver = new BootReceiver();
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(Intent.ACTION_BOOT_COMPLETED);
|
|
||||||
bootReceiver.onReceive(context, intent);
|
|
||||||
// Check if the service is started
|
|
||||||
Assert.assertFalse(isServiceExisted(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the behavior when receive notification which is not the phone boot.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOnReceive_noStartService() {
|
|
||||||
// Make sure no TrackRecordingService
|
|
||||||
Intent stopIntent = new Intent(context, TrackRecordingService.class);
|
|
||||||
context.stopService(stopIntent);
|
|
||||||
Assert.assertFalse(isServiceExisted(context));
|
|
||||||
|
|
||||||
BootReceiver bootReceiver = new BootReceiver();
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(Intent.ACTION_BUG_REPORT);
|
|
||||||
bootReceiver.onReceive(context, intent);
|
|
||||||
|
|
||||||
// Check if the service is not started
|
|
||||||
Assert.assertFalse(isServiceExisted(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a service is started in a context.
|
|
||||||
*
|
|
||||||
* @param context the context for checking a service
|
|
||||||
*/
|
|
||||||
private boolean isServiceExisted(Context context) {
|
|
||||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
||||||
Assert.assertNotNull(activityManager);
|
|
||||||
|
|
||||||
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
|
|
||||||
|
|
||||||
for (int i = 0; i < serviceList.size(); i++) {
|
|
||||||
RunningServiceInfo serviceInfo = serviceList.get(i);
|
|
||||||
ComponentName componentName = serviceInfo.service;
|
|
||||||
if (componentName.getClassName().equals(SERVICE_NAME)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
* License for the specific language governing permissions and limitations under
|
* License for the specific language governing permissions and limitations under
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -23,6 +23,8 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link ChartValueSeries}.
|
* Tests {@link ChartValueSeries}.
|
||||||
*
|
*
|
||||||
@@ -35,21 +35,19 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unit test for {@link ContentProviderUtilsImpl}.
|
* A unit test for {@link ContentProviderUtils}.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
* @author Youtao Liu
|
* @author Youtao Liu
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class CustomContentProviderUtilsImplTest {
|
public class CustomContentProviderUtilsTest {
|
||||||
private static final String NAME_PREFIX = "test name";
|
private static final String NAME_PREFIX = "test name";
|
||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
@@ -69,13 +67,13 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
providerUtils.deleteAllTracks(context);
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noPoints() {
|
public void testLocationIterator_noPoints() {
|
||||||
testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 0, 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -105,36 +103,36 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noBatchAscending() {
|
public void testLocationIterator_noBatchAscending() {
|
||||||
testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 100, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 50, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_noBatchDescending() {
|
public void testLocationIterator_noBatchDescending() {
|
||||||
testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 100, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 50, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_batchAscending() {
|
public void testLocationIterator_batchAscending() {
|
||||||
testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 11, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 25, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_batchDescending() {
|
public void testLocationIterator_batchDescending() {
|
||||||
testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 50, 11, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(2, 50, 25, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocationIterator_largeTrack() {
|
public void testLocationIterator_largeTrack() {
|
||||||
testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
testIterator(1, 20000, 2000, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) {
|
||||||
long lastPointId = initializeTrack(trackId, numPoints);
|
long lastPointId = initializeTrack(trackId, numPoints);
|
||||||
((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
|
((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize);
|
||||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -173,7 +171,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
// Load all inserted locations.
|
// Load all inserted locations.
|
||||||
long lastPointId = -1;
|
long lastPointId = -1;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
lastPointId = it.getLocationId();
|
lastPointId = it.getLocationId();
|
||||||
@@ -189,7 +187,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createTrack(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrack() {
|
public void testCreateTrack() {
|
||||||
@@ -216,7 +214,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)}
|
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteAllTracks() {
|
public void testDeleteAllTracks() {
|
||||||
@@ -245,7 +243,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}.
|
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTrack() {
|
public void testDeleteTrack() {
|
||||||
@@ -280,7 +278,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getAllTracks()}
|
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllTracks() {
|
public void testGetAllTracks() {
|
||||||
@@ -293,7 +291,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrack()}
|
* Tests the method {@link ContentProviderUtils#getLastTrack()}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastTrack() {
|
public void testGetLastTrack() {
|
||||||
@@ -303,7 +301,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrack(long)}
|
* Tests the method {@link ContentProviderUtils#getTrack(long)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrack() {
|
public void testGetTrack() {
|
||||||
@@ -313,7 +311,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)}
|
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateTrack() {
|
public void testUpdateTrack() {
|
||||||
@@ -330,7 +328,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateContentValues_waypoint() {
|
public void testCreateContentValues_waypoint() {
|
||||||
@@ -368,7 +366,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
waypoint.setLocation(location);
|
waypoint.setLocation(location);
|
||||||
providerUtils.insertWaypoint(waypoint);
|
providerUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||||
|
|
||||||
long waypointId = System.currentTimeMillis();
|
long waypointId = System.currentTimeMillis();
|
||||||
waypoint.setId(waypointId);
|
waypoint.setId(waypointId);
|
||||||
@@ -381,7 +379,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWaypoint() {
|
public void testCreateWaypoint() {
|
||||||
@@ -416,7 +414,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||||
* when there is only one waypoint in the track.
|
* when there is only one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -437,7 +435,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
return CustomContentProviderUtilsTest.MOCK_DESC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -452,7 +450,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)}
|
||||||
* when there is more than one waypoint in the track.
|
* when there is more than one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -495,7 +493,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
DescriptionGenerator descriptionGenerator = new DescriptionGenerator() {
|
||||||
@Override
|
@Override
|
||||||
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
public String generateWaypointDescription(TripStatistics tripStatistics) {
|
||||||
return CustomContentProviderUtilsImplTest.MOCK_DESC;
|
return CustomContentProviderUtilsTest.MOCK_DESC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -508,12 +506,12 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator);
|
||||||
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
Assert.assertNull(providerUtils.getWaypoint(waypoint1Id));
|
||||||
|
|
||||||
Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id)
|
||||||
.getDescription());
|
.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getFirstWaypointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetFirstWaypointId() {
|
public void testGetFirstWaypointId() {
|
||||||
@@ -535,7 +533,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}.
|
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long, WaypointType)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetNextWaypointNumber() {
|
public void testGetNextWaypointNumber() {
|
||||||
@@ -594,8 +592,8 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and
|
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
|
||||||
* {@link ContentProviderUtilsImpl#getWaypoint(long)}.
|
* {@link ContentProviderUtils#getWaypoint(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInsertAndGetWaypoint() {
|
public void testInsertAndGetWaypoint() {
|
||||||
@@ -612,7 +610,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint() {
|
public void testUpdateWaypoint() {
|
||||||
@@ -634,7 +632,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}.
|
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBulkInsertTrackPoint() {
|
public void testBulkInsertTrackPoint() {
|
||||||
@@ -650,7 +648,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrackPoint() {
|
public void testCreateTrackPoint() {
|
||||||
@@ -710,7 +708,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}.
|
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInsertTrackPoint() {
|
public void testInsertTrackPoint() {
|
||||||
@@ -724,7 +722,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getFirstTrackPointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetFirstTrackPointId() {
|
public void testGetFirstTrackPointId() {
|
||||||
@@ -737,7 +735,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}.
|
* Tests the method {@link ContentProviderUtils#getLastTrackPointId(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastTrackPointId() {
|
public void testGetLastTrackPointId() {
|
||||||
@@ -750,7 +748,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}.
|
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetLastValidTrackPoint() {
|
public void testGetLastValidTrackPoint() {
|
||||||
@@ -764,7 +762,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_desc() {
|
public void testGetTrackPointCursor_desc() {
|
||||||
@@ -783,7 +781,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_asc() {
|
public void testGetTrackPointCursor_asc() {
|
||||||
@@ -802,7 +800,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_desc() {
|
public void testGetTrackPointLocationIterator_desc() {
|
||||||
@@ -818,7 +816,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
long startTrackPointId = trackpointIds[9];
|
long startTrackPointId = trackpointIds[9];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -829,7 +827,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_asc() {
|
public void testGetTrackPointLocationIterator_asc() {
|
||||||
@@ -845,7 +843,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
|
|
||||||
long startTrackPointId = trackpointIds[0];
|
long startTrackPointId = trackpointIds[0];
|
||||||
|
|
||||||
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY);
|
LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY);
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(locationIterator.hasNext());
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -892,7 +890,7 @@ public class CustomContentProviderUtilsImplTest {
|
|||||||
/**
|
/**
|
||||||
* Checks the value of a location.
|
* Checks the value of a location.
|
||||||
*
|
*
|
||||||
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)}
|
* @param i the index of this location which created in the method {@link CustomContentProviderUtilsTest#getTrack(long, int)}
|
||||||
* @param location the location to be checked
|
* @param location the location to be checked
|
||||||
*/
|
*/
|
||||||
private void checkLocation(int i, Location location) {
|
private void checkLocation(int i, Location location) {
|
||||||
@@ -58,7 +58,7 @@ public class SearchEngineTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
engine = new SearchEngine(providerUtils);
|
engine = new SearchEngine(providerUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,20 @@
|
|||||||
package de.dennisguse.opentracks.fragments;
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.ChartView;
|
|
||||||
import de.dennisguse.opentracks.TrackStubUtils;
|
import de.dennisguse.opentracks.TrackStubUtils;
|
||||||
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
@@ -44,10 +47,23 @@ public class ChartFragmentTest {
|
|||||||
|
|
||||||
private ChartFragment chartFragment;
|
private ChartFragment chartFragment;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void finalTearDown() {
|
||||||
|
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
chartFragment = new ChartFragment();
|
boolean chartByDistance = false;
|
||||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext()));
|
chartFragment = new ChartFragment(chartByDistance) {
|
||||||
|
};
|
||||||
|
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||||
@@ -156,7 +155,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||||
@@ -211,7 +210,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||||
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
when(contentProviderUtils.getFirstTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_0);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||||
@@ -110,7 +109,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getLong(context, R.string.recording_track_id_key))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class TrackRecordingServiceNotificationManagerTest {
|
||||||
|
|
||||||
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Location locationMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationCompat.Builder notificationCompatBuilder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||||
|
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||||
|
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||||
|
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||||
|
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||||
|
|
||||||
|
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||||
|
|
||||||
|
// when
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
subject.updateLocation(context, locationMock, 1000);
|
||||||
|
subject.updateLocation(context, locationMock, 100);
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||||
|
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,17 +71,12 @@ public class TrackRecordingServiceTest {
|
|||||||
private Context context = ApplicationProvider.getApplicationContext();
|
private Context context = ApplicationProvider.getApplicationContext();
|
||||||
private ContentProviderUtils providerUtils;
|
private ContentProviderUtils providerUtils;
|
||||||
|
|
||||||
private long trackId = Math.abs(new Random().nextLong());
|
private final long trackId = Math.abs(new Random().nextLong());
|
||||||
|
|
||||||
static Intent createStartIntent(Context context) {
|
static Intent createStartIntent(Context context) {
|
||||||
return new Intent(context, TrackRecordingService.class);
|
return new Intent(context, TrackRecordingService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
|
|
||||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
|
||||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_timeout_key, timeoutMins);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
// Set up the mock content resolver
|
// Set up the mock content resolver
|
||||||
@@ -89,15 +84,12 @@ public class TrackRecordingServiceTest {
|
|||||||
};
|
};
|
||||||
customContentProvider.attachInfo(context, null);
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
sharedPreferences.edit().clear().apply();
|
sharedPreferences.edit().clear().apply();
|
||||||
|
|
||||||
// Disable auto resume by default.
|
|
||||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
|
||||||
|
|
||||||
// Ensure that the database is empty before every test
|
// Ensure that the database is empty before every test
|
||||||
providerUtils.deleteAllTracks(context);
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
@@ -128,124 +120,6 @@ public class TrackRecordingServiceTest {
|
|||||||
Assert.assertNotNull(service);
|
Assert.assertNotNull(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testResumeAfterReboot_shouldResume() throws Exception {
|
|
||||||
// Insert a dummy track and mark it as recording track.
|
|
||||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent(context);
|
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
mServiceRule.startService(startIntent);
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
|
||||||
|
|
||||||
// then
|
|
||||||
Assert.assertNotNull(service);
|
|
||||||
|
|
||||||
// We expect to resume the previous track.
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
|
||||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Simulate recording a track.
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
|
||||||
mServiceRule.unbindService();
|
|
||||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
|
|
||||||
Intent startIntent = createStartIntent(context);
|
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
mServiceRule.startService(startIntent);
|
|
||||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
|
|
||||||
// then
|
|
||||||
Assert.assertNotNull(service);
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
|
||||||
// Insert a dummy track and mark it as recording track.
|
|
||||||
createDummyTrack(trackId, System.currentTimeMillis(), false);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent(context);
|
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
mServiceRule.startService(startIntent);
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
|
||||||
|
|
||||||
// then
|
|
||||||
Assert.assertNotNull(service);
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it was stopped.
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
|
||||||
// Insert a dummy track last updated 20 min ago.
|
|
||||||
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent(context);
|
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
mServiceRule.startService(startIntent);
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
|
||||||
|
|
||||||
// then
|
|
||||||
Assert.assertNotNull(service);
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it has expired.
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
|
||||||
// Insert a dummy track.
|
|
||||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
|
||||||
|
|
||||||
// Set the number of attempts to max.
|
|
||||||
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent(context);
|
|
||||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
mServiceRule.startService(startIntent);
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
|
||||||
|
|
||||||
//then
|
|
||||||
Assert.assertNotNull(service);
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because there were already too many attempts.
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_noTracks() throws Exception {
|
public void testRecording_noTracks() throws Exception {
|
||||||
@@ -289,16 +163,18 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||||
createDummyTrack(trackId, -1L, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
service.startNewTrack();
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
|
long trackId = service.getRecordingTrackId();
|
||||||
|
|
||||||
long newTrackId = service.startNewTrack();
|
long newTrackId = service.startNewTrack();
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||||
|
|
||||||
Assert.assertEquals(trackId, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
|
|
||||||
|
service.endCurrentTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@@ -310,48 +186,11 @@ public class TrackRecordingServiceTest {
|
|||||||
// Ending the current track when there is no recording should not result in any error.
|
// Ending the current track when there is no recording should not result in any error.
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
// NOTE: Do not use to create a track that is currently recording.
|
||||||
@Test
|
|
||||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
|
||||||
Assert.assertEquals(-1L, waypointId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
|
||||||
createDummyTrack(trackId, -1L, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
Assert.assertFalse(service.isPaused());
|
|
||||||
insertLocation(service);
|
|
||||||
|
|
||||||
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
|
||||||
Assert.assertNotEquals(-1L, waypointId1);
|
|
||||||
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
|
||||||
Assert.assertNotEquals(-1L, waypointId2);
|
|
||||||
|
|
||||||
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
|
|
||||||
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
|
|
||||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
|
||||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
|
||||||
Assert.assertNotNull(wpt.getLocation());
|
|
||||||
Assert.assertNotNull(wpt.getTripStatistics());
|
|
||||||
// TODO check the rest of the params.
|
|
||||||
|
|
||||||
// TODO: Check waypoint 2.
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||||
Track dummyTrack = new Track();
|
Track dummyTrack = new Track();
|
||||||
dummyTrack.setId(id);
|
dummyTrack.setId(id);
|
||||||
@@ -399,12 +238,12 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||||
createDummyTrack(trackId, -1L, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
service.startNewTrack();
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
insertLocation(service);
|
insertLocation(service);
|
||||||
|
|
||||||
|
long trackId = service.getRecordingTrackId();
|
||||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||||
Assert.assertNotEquals(-1L, waypointId);
|
Assert.assertNotEquals(-1L, waypointId);
|
||||||
Waypoint wpt = providerUtils.getWaypoint(waypointId);
|
Waypoint wpt = providerUtils.getWaypoint(waypointId);
|
||||||
@@ -415,5 +254,7 @@ public class TrackRecordingServiceTest {
|
|||||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||||
Assert.assertNotNull(wpt.getLocation());
|
Assert.assertNotNull(wpt.getLocation());
|
||||||
Assert.assertNull(wpt.getTripStatistics());
|
Assert.assertNull(wpt.getTripStatistics());
|
||||||
|
|
||||||
|
service.endCurrentTrack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,15 +68,12 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
};
|
};
|
||||||
customContentProvider.attachInfo(context, null);
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
providerUtils = ContentProviderUtils.Factory.get(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
sharedPreferences.edit().clear().apply();
|
sharedPreferences.edit().clear().apply();
|
||||||
|
|
||||||
// Disable auto resume by default.
|
|
||||||
TrackRecordingServiceTest.updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
|
||||||
|
|
||||||
// Ensure that the database is empty before every test
|
// Ensure that the database is empty before every test
|
||||||
providerUtils.deleteAllTracks(context);
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
@@ -103,7 +100,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT);
|
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +122,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +136,8 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,31 +148,10 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
|
|
||||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setString(context, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
|
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +165,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +179,8 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
|
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +203,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
Track track = providerUtils.getTrack(id);
|
Track track = providerUtils.getTrack(id);
|
||||||
Assert.assertNotNull(track);
|
Assert.assertNotNull(track);
|
||||||
Assert.assertEquals(id, track.getId());
|
Assert.assertEquals(id, track.getId());
|
||||||
Assert.assertEquals(id, PreferencesUtils.getLong(context, R.string.recording_track_id_key));
|
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||||
|
|
||||||
// Insert a few points, markers and statistics.
|
// Insert a few points, markers and statistics.
|
||||||
@@ -240,9 +218,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
location.setBearing(3.0f);
|
location.setBearing(3.0f);
|
||||||
service.insertTrackPoint(location);
|
service.insertTrackPoint(location);
|
||||||
|
|
||||||
if (i % 10 == 0) {
|
if (i % 7 == 0) {
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
|
||||||
} else if (i % 7 == 0) {
|
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,10 +60,15 @@ public class FileUtilsTest extends TestCase {
|
|||||||
*/
|
*/
|
||||||
public void testSanitizeFileName() {
|
public void testSanitizeFileName() {
|
||||||
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
|
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
|
||||||
String expected = "Swim_ming-^across_the_ pacific (ocean)_";
|
String expected = "Swim_ming-^across_the_ pacific (ocean).";
|
||||||
assertEquals(expected, FileUtils.sanitizeFileName(name));
|
assertEquals(expected, FileUtils.sanitizeFileName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSanitizeFileNameWithSuffix() {
|
||||||
|
String name = "FileName.jpeg";
|
||||||
|
assertEquals(name, FileUtils.sanitizeFileName(name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
* Tests {@link FileUtils#sanitizeFileName(String)} with i18n characters (in Chinese and Russian).
|
||||||
* Verifies that they are allowed.
|
* Verifies that they are allowed.
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="3101"
|
android:versionCode="3195"
|
||||||
android:versionName="v3.2.3"> <!-- For FDroid. -->
|
android:versionName="v3.2.10"> <!-- For FDroid. -->
|
||||||
<!-- Permission to initialize services -->
|
<!-- Permission to initialize services -->
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
@@ -25,6 +25,7 @@ limitations under the License.
|
|||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:theme="@style/ThemeCustom">
|
android:theme="@style/ThemeCustom">
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
@@ -32,6 +33,10 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".DeleteActivity" />
|
<activity android:name=".DeleteActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".HelpActivity"
|
||||||
|
android:label="@string/menu_help" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.importer.ImportActivity"
|
android:name=".io.file.importer.ImportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent">
|
android:theme="@style/ThemeCustomTransparent">
|
||||||
@@ -50,10 +55,10 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".MarkerEditActivity" />
|
|
||||||
|
|
||||||
<activity android:name=".MarkerDetailActivity" />
|
<activity android:name=".MarkerDetailActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".MarkerEditActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MarkerListActivity"
|
android:name=".MarkerListActivity"
|
||||||
android:label="@string/menu_markers">
|
android:label="@string/menu_markers">
|
||||||
@@ -95,7 +100,6 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
<activity android:name=".settings.SettingsActivity" />
|
||||||
|
|
||||||
<!-- Services -->
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".content.ShareContentProvider"
|
android:name=".content.ShareContentProvider"
|
||||||
android:authorities="${applicationId}.content"
|
android:authorities="${applicationId}.content"
|
||||||
@@ -112,18 +116,11 @@ limitations under the License.
|
|||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<!-- Receivers -->
|
|
||||||
<receiver android:name=".BootReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<!-- Providers -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.TrackRecordingService"
|
android:name=".services.TrackRecordingService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="location|connectedDevice"
|
||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/recording_service" />
|
android:label="@string/recording_service" />
|
||||||
</application>
|
</application>
|
||||||
@@ -138,44 +135,23 @@ limitations under the License.
|
|||||||
android:name="android.hardware.location.gps"
|
android:name="android.hardware.location.gps"
|
||||||
android:required="true" />
|
android:required="true" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.bluetooth"
|
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<!-- Permissions to access sensors -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.faketouch"
|
|
||||||
android:required="false" />
|
|
||||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.sensor.compass"
|
android:name="android.hardware.sensor.compass"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<!-- Permission to read/write external storage -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.telephony"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.telephony.gsm"
|
|
||||||
android:required="false" />
|
|
||||||
<!-- Required features -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<!-- Optional features -->
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.touchscreen.multitouch"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -17,11 +17,12 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.TTSUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
@@ -32,7 +33,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Set volume control stream for text to speech
|
// Set volume control stream for text to speech
|
||||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
setVolumeControlStream(TTSUtils.getTTSStream());
|
||||||
|
|
||||||
setContentView(getLayoutResId());
|
setContentView(getLayoutResId());
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||||
stopRecording = true;
|
stopRecording = true;
|
||||||
} else {
|
} else {
|
||||||
long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key);
|
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||||
for (long trackId : trackIds) {
|
for (long trackId : trackIds) {
|
||||||
if (trackId == recordingTrackId) {
|
if (trackId == recordingTrackId) {
|
||||||
stopRecording = true;
|
stopRecording = true;
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
|
||||||
|
|
||||||
import static android.content.Intent.ACTION_BOOT_COMPLETED;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class react to the BOOT_COMPLETED broadcast.
|
|
||||||
* <p>
|
|
||||||
* One example of a broadcast message that this class is interested in,
|
|
||||||
* is notification about the phone boot. We may want to resume a previously
|
|
||||||
* started tracking session if the phone crashed (hopefully not), or the user
|
|
||||||
* decided to swap the battery or some external event occurred which forced
|
|
||||||
* a phone reboot.
|
|
||||||
* <p>
|
|
||||||
* This class simply delegates to {@link TrackRecordingService} to make a
|
|
||||||
* decision whether to continue with the previous track (if any), or just
|
|
||||||
* abandon it.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class BootReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private static final String TAG = BootReceiver.class.getSimpleName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction());
|
|
||||||
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
|
||||||
Intent startIntent = new Intent(context, TrackRecordingService.class)
|
|
||||||
.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
context.startService(startIntent);
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "BootReceiver: unsupported action");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
public class HelpActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutResId() {
|
||||||
|
return R.layout.help;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||||
|
|
||||||
markerIds = new ArrayList<>();
|
markerIds = new ArrayList<>();
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
||||||
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
||||||
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
|
||||||
waypointMarkerType.setAdapter(adapter);
|
waypointMarkerType.setAdapter(adapter);
|
||||||
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
||||||
|
|
||||||
@@ -127,7 +126,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
if (newMarker) {
|
if (newMarker) {
|
||||||
statisticsSection.setVisibility(View.GONE);
|
statisticsSection.setVisibility(View.GONE);
|
||||||
waypointSection.setVisibility(View.VISIBLE);
|
waypointSection.setVisibility(View.VISIBLE);
|
||||||
int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT);
|
||||||
if (nextWaypointNumber == -1) {
|
if (nextWaypointNumber == -1) {
|
||||||
nextWaypointNumber = 0;
|
nextWaypointNumber = 0;
|
||||||
}
|
}
|
||||||
@@ -136,7 +135,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointMarkerType.setText("");
|
waypointMarkerType.setText("");
|
||||||
waypointDescription.setText("");
|
waypointDescription.setText("");
|
||||||
} else {
|
} else {
|
||||||
waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId);
|
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||||
if (waypoint == null) {
|
if (waypoint == null) {
|
||||||
Log.d(TAG, "waypoint is null");
|
Log.d(TAG, "waypoint is null");
|
||||||
finish();
|
finish();
|
||||||
@@ -182,6 +181,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||||
waypoint.setDescription(waypointDescription.getText().toString());
|
waypoint.setDescription(waypointDescription.getText().toString());
|
||||||
}
|
}
|
||||||
ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint);
|
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ import androidx.loader.content.Loader;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.content.WaypointsColumns;
|
import de.dennisguse.opentracks.content.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,21 +69,20 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||||
* class. Anonymous inner class will get garbage collected.
|
* class. Anonymous inner class will get garbage collected.
|
||||||
*/
|
*/
|
||||||
private final OnSharedPreferenceChangeListener
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
// Note that the key can be null
|
// Note that the key can be null
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(MarkerListActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(MarkerListActivity.this, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(MarkerListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -128,7 +127,9 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
@@ -150,7 +151,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
|
|
||||||
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
||||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||||
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
||||||
@@ -159,13 +159,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
||||||
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
||||||
|
|
||||||
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS;
|
int iconId = MarkerUtils.ICON_ID;
|
||||||
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin;
|
|
||||||
String name = cursor.getString(nameIndex);
|
String name = cursor.getString(nameIndex);
|
||||||
long time = cursor.getLong(timeIndex);
|
long time = cursor.getLong(timeIndex);
|
||||||
String category = statistics ? null : cursor.getString(categoryIndex);
|
String category = cursor.getString(categoryIndex);
|
||||||
String description = statistics ? null : cursor.getString(descriptionIndex);
|
String description = cursor.getString(descriptionIndex);
|
||||||
String photoUrl = cursor.getString(photoUrlIndex);
|
String photoUrl = cursor.getString(photoUrlIndex);
|
||||||
|
//TODO also show latitude and longitude in list
|
||||||
double latitude = cursor.getDouble(latitudeIndex);
|
double latitude = cursor.getDouble(latitudeIndex);
|
||||||
double longitude = cursor.getDouble(longitudeIndex);
|
double longitude = cursor.getDouble(longitudeIndex);
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
|
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
|
||||||
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
|
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
|
|||||||
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
@@ -55,6 +54,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
|||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
@@ -101,7 +101,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
|
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
// Callback when an item is selected in the contextual action mode
|
// Callback when an item is selected in the contextual action mode
|
||||||
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
private ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
|
||||||
@@ -143,14 +143,14 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.stats_units_key))) {
|
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.stats_units_key, key)) {
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
|
metricUnits = PreferencesUtils.isMetricUnits(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(SearchListActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(SearchListActivity.this, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(SearchListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(SearchListActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -168,7 +168,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
searchEngine = new SearchEngine(contentProviderUtils);
|
searchEngine = new SearchEngine(contentProviderUtils);
|
||||||
@@ -391,11 +393,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
|
|
||||||
|
|
||||||
resultMap.put(IS_RECORDING_FIELD, false);
|
resultMap.put(IS_RECORDING_FIELD, false);
|
||||||
resultMap.put(IS_PAUSED_FIELD, true);
|
resultMap.put(IS_PAUSED_FIELD, true);
|
||||||
resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin);
|
resultMap.put(ICON_ID_FIELD, MarkerUtils.ICON_ID);
|
||||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
|
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
|
||||||
resultMap.put(NAME_FIELD, waypoint.getName());
|
resultMap.put(NAME_FIELD, waypoint.getName());
|
||||||
// Display the marker's track name in the total time field
|
// Display the marker's track name in the total time field
|
||||||
@@ -403,8 +403,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
resultMap.put(TOTAL_DISTANCE_FIELD, null);
|
resultMap.put(TOTAL_DISTANCE_FIELD, null);
|
||||||
resultMap.put(MARKER_COUNT_FIELD, 0);
|
resultMap.put(MARKER_COUNT_FIELD, 0);
|
||||||
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
||||||
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
|
resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
|
||||||
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription());
|
resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
|
||||||
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
||||||
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
||||||
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
|
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.widget.TabHost;
|
|||||||
import android.widget.TabHost.TabContentFactory;
|
import android.widget.TabHost.TabContentFactory;
|
||||||
import android.widget.TabWidget;
|
import android.widget.TabWidget;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
@@ -79,6 +80,7 @@ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
TabInfo info = tabInfos.get(position);
|
TabInfo info = tabInfos.get(position);
|
||||||
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
|
return Fragment.instantiate(context, info.clss.getName(), info.bundle);
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ import de.dennisguse.opentracks.content.TrackDataHub;
|
|||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartDistanceFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.ChartTimeFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.FrequencyDialogFragment;
|
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
@@ -94,8 +94,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
private boolean showOnLockScreen = PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN;
|
|
||||||
|
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -118,25 +117,29 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/*
|
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
// Note that sharedPreferenceChangeListener cannot be an anonymous inner class. Anonymous inner class will get garbage collected.
|
||||||
* class. Anonymous inner class will get garbage collected.
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
*/
|
|
||||||
private final OnSharedPreferenceChangeListener
|
|
||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(TrackDetailActivity.this, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.recording_track_paused_key))) {
|
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key))) {
|
|
||||||
showOnLockScreen = PreferencesUtils.getBoolean(TrackDetailActivity.this, R.string.trackdetail_show_on_lockscreen_while_recording_key, PreferencesUtils.SHOW_TRACKDETAIL_WHILE_RECORDING_ON_LOCKSCREEN);
|
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||||
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
|
setLockscreenPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||||
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
@@ -156,8 +159,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private MenuItem insertPhotoMenuItem;
|
private MenuItem insertPhotoMenuItem;
|
||||||
private MenuItem markerListMenuItem;
|
private MenuItem markerListMenuItem;
|
||||||
private MenuItem shareMenuItem;
|
private MenuItem shareMenuItem;
|
||||||
private MenuItem voiceFrequencyMenuItem;
|
|
||||||
private MenuItem splitFrequencyMenuItem;
|
|
||||||
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -187,11 +188,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||||
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
||||||
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
@@ -208,8 +211,11 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
|
TabSpec statsTabSpec = tabHost.newTabSpec(StatsFragment.STATS_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_stats_tab));
|
||||||
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
tabsAdapter.addTab(statsTabSpec, StatsFragment.class, null);
|
||||||
|
|
||||||
TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.track_detail_chart_tab));
|
TabSpec chartTimeTabSpec = tabHost.newTabSpec(ChartTimeFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_time));
|
||||||
tabsAdapter.addTab(chartTabSpec, ChartFragment.class, null);
|
tabsAdapter.addTab(chartTimeTabSpec, ChartTimeFragment.class, null);
|
||||||
|
|
||||||
|
TabSpec chartDistanceTabSpec = tabHost.newTabSpec(ChartDistanceFragment.CHART_FRAGMENT_TAG).setIndicator(getString(R.string.settings_chart_by_distance));
|
||||||
|
tabsAdapter.addTab(chartDistanceTabSpec, ChartDistanceFragment.class, null);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
|
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY));
|
||||||
@@ -226,16 +232,31 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public void onAttachedToWindow() {
|
public void onAttachedToWindow() {
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLockscreenPolicy() {
|
private void setLockscreenPolicy() {
|
||||||
|
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||||
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
setShowWhenLocked(showOnLockScreen);
|
setShowWhenLocked(showOnLockScreen);
|
||||||
} else {
|
} else if (showOnLockScreen) {
|
||||||
if (showOnLockScreen)
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
} else {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setScreenOnPolicy() {
|
||||||
|
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
||||||
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
|
if (keepScreenOn) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
} else {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +296,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
|
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
|
||||||
if (photoUri != null) {
|
if (photoUri != null) {
|
||||||
@@ -316,8 +337,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
||||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
||||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||||
voiceFrequencyMenuItem = menu.findItem(R.id.track_detail_voice_frequency);
|
|
||||||
splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency);
|
|
||||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -359,12 +378,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_voice_frequency:
|
|
||||||
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT, R.string.menu_voice_frequency);
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_split_frequency:
|
|
||||||
FrequencyDialogFragment.showDialog(getSupportFragmentManager(), R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT, R.string.menu_split_frequency);
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_edit:
|
case R.id.track_detail_edit:
|
||||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
@@ -418,16 +431,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
return trackDataHub;
|
return trackDataHub;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the track id.
|
|
||||||
*/
|
|
||||||
public long getTrackId() {
|
|
||||||
return trackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the data in the intent.
|
|
||||||
*/
|
|
||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||||
@@ -466,8 +469,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
||||||
shareMenuItem.setVisible(!isRecording());
|
shareMenuItem.setVisible(!isRecording());
|
||||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
voiceFrequencyMenuItem.setVisible(isRecording());
|
|
||||||
splitFrequencyMenuItem.setVisible(isRecording());
|
|
||||||
String title;
|
String title;
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
track = contentProviderUtils.getTrack(trackId);
|
track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.e(TAG, "No track for " + trackId);
|
Log.e(TAG, "No track for " + trackId);
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean recordingTrackPaused = PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
|
* Note that sharedPreferenceChangeListener cannot be an anonymous inner class.
|
||||||
@@ -135,22 +135,17 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(TrackListActivity.this, R.string.stats_units_key))) {
|
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(
|
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
PreferencesUtils.getKey(TrackListActivity.this, R.string.recording_track_id_key))) {
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||||
recordingTrackId = PreferencesUtils.getLong(
|
|
||||||
TrackListActivity.this, R.string.recording_track_id_key);
|
|
||||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
trackRecordingServiceConnection.startAndBind();
|
trackRecordingServiceConnection.startAndBind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(
|
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
TrackListActivity.this, R.string.recording_track_paused_key))) {
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(TrackListActivity.this,
|
|
||||||
R.string.recording_track_paused_key,
|
|
||||||
PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -169,7 +164,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Menu items
|
// Menu items
|
||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
private MenuItem startGpsMenuItem;
|
private MenuItem startGpsMenuItem;
|
||||||
private MenuItem deleteAllMenuItem;
|
// private MenuItem deleteAllMenuItem;
|
||||||
|
|
||||||
private final OnClickListener stopListener = new OnClickListener() {
|
private final OnClickListener stopListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -226,12 +221,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
startRecording();
|
startRecording();
|
||||||
} else {
|
} else if (recordingTrackPaused) {
|
||||||
if (recordingTrackPaused) {
|
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
@@ -243,14 +237,15 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
trackController.update(true, true);
|
trackController.update(true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||||
@@ -376,7 +371,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||||
|
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -430,9 +424,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_list_delete_all:
|
|
||||||
deleteTracks(new long[]{-1L});
|
|
||||||
return true;
|
|
||||||
case R.id.track_list_settings:
|
case R.id.track_list_settings:
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -500,9 +491,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
|
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deleteAllMenuItem != null) {
|
|
||||||
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
@@ -23,8 +23,6 @@ import android.graphics.Paint.Align;
|
|||||||
import android.graphics.Paint.Style;
|
import android.graphics.Paint.Style;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||||
@@ -203,7 +201,6 @@ class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Gets the interval value.
|
* Gets the interval value.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
|
||||||
int getInterval() {
|
int getInterval() {
|
||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
@@ -218,7 +215,6 @@ class ChartValueSeries {
|
|||||||
/**
|
/**
|
||||||
* Gets the max marker value.
|
* Gets the max marker value.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
|
||||||
int getMaxMarkerValue() {
|
int getMaxMarkerValue() {
|
||||||
return maxMarkerValue;
|
return maxMarkerValue;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -27,33 +27,40 @@ import android.graphics.Paint.Style;
|
|||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.VelocityTracker;
|
import android.view.ScaleGestureDetector;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
|
import androidx.core.view.GestureDetectorCompat;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visualization of the chart.
|
* Visualization of the chart.
|
||||||
|
* Provides support for zooming (via pinch), scrolling, flinging, and selecting shown markers (single touch).
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class ChartView extends View {
|
public class ChartView extends View {
|
||||||
|
|
||||||
public static final float MEDIUM_TEXT_SIZE = 18f;
|
static final float MEDIUM_TEXT_SIZE = 18f;
|
||||||
public static final float SMALL_TEXT_SIZE = 12f;
|
static final float SMALL_TEXT_SIZE = 12f;
|
||||||
|
|
||||||
public static final int Y_AXIS_INTERVALS = 5;
|
static final int Y_AXIS_INTERVALS = 5;
|
||||||
|
|
||||||
public static final int NUM_SERIES = 6;
|
public static final int NUM_SERIES = 6;
|
||||||
public static final int ELEVATION_SERIES = 0;
|
public static final int ELEVATION_SERIES = 0;
|
||||||
@@ -74,25 +81,28 @@ public class ChartView extends View {
|
|||||||
private static final int SPACER = 4;
|
private static final int SPACER = 4;
|
||||||
private static final int Y_AXIS_OFFSET = 16;
|
private static final int Y_AXIS_OFFSET = 16;
|
||||||
|
|
||||||
|
//TODO Determine from actual size of the used drawable
|
||||||
|
private static final float WAYPOINT_X_ANCHOR = 13f / 48f;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
X_FRACTION_FORMAT.setMaximumFractionDigits(1);
|
X_FRACTION_FORMAT.setMaximumFractionDigits(1);
|
||||||
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
|
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
|
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
|
||||||
private final ArrayList<double[]> chartData = new ArrayList<>();
|
private final List<double[]> chartData = new ArrayList<>();
|
||||||
private final ArrayList<Waypoint> waypoints = new ArrayList<>();
|
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||||
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
|
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
|
||||||
private final Paint axisPaint;
|
private final Paint axisPaint;
|
||||||
private final Paint xAxisMarkerPaint;
|
private final Paint xAxisMarkerPaint;
|
||||||
private final Paint gridPaint;
|
private final Paint gridPaint;
|
||||||
|
private final Paint markerPaint;
|
||||||
private final Drawable pointer;
|
private final Drawable pointer;
|
||||||
|
private final Drawable waypointMarker;
|
||||||
private final int markerWidth;
|
private final int markerWidth;
|
||||||
private final int markerHeight;
|
private final int markerHeight;
|
||||||
private final Scroller scroller;
|
private final Scroller scroller;
|
||||||
private double maxX = 1.0;
|
private double maxX = 1.0;
|
||||||
private VelocityTracker velocityTracker = null;
|
|
||||||
private float lastMotionEventX = -1;
|
|
||||||
private int zoomLevel = 1;
|
private int zoomLevel = 1;
|
||||||
|
|
||||||
private int leftBorder = BORDER;
|
private int leftBorder = BORDER;
|
||||||
@@ -107,18 +117,84 @@ public class ChartView extends View {
|
|||||||
private int effectiveWidth = 0;
|
private int effectiveWidth = 0;
|
||||||
private int effectiveHeight = 0;
|
private int effectiveHeight = 0;
|
||||||
|
|
||||||
private boolean chartByDistance = true;
|
private boolean chartByDistance;
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean reportSpeed = true;
|
private boolean reportSpeed = true;
|
||||||
private boolean showPointer = false;
|
private boolean showPointer = false;
|
||||||
|
|
||||||
/**
|
private GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||||
* Constructor.
|
|
||||||
*
|
@Override
|
||||||
* @param context the context
|
public boolean onDown(MotionEvent e) {
|
||||||
*/
|
if (!scroller.isFinished()) {
|
||||||
public ChartView(Context context) {
|
scroller.abortAnimation();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||||
|
if (Math.abs(distanceX) > 0) {
|
||||||
|
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
||||||
|
if (availableToScroll > 0) {
|
||||||
|
scrollBy(Math.min(availableToScroll, (int) distanceX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||||
|
fling((int) -velocityX);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapConfirmed(MotionEvent event) {
|
||||||
|
// Check if the y event is within markerHeight of the marker center
|
||||||
|
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
|
||||||
|
int minDistance = Integer.MAX_VALUE;
|
||||||
|
Waypoint nearestWaypoint = null;
|
||||||
|
synchronized (waypoints) {
|
||||||
|
for (Waypoint waypoint : waypoints) {
|
||||||
|
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
nearestWaypoint = waypoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearestWaypoint != null && minDistance < markerWidth) {
|
||||||
|
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
||||||
|
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScale(ScaleGestureDetector detector) {
|
||||||
|
float scaleFactor = detector.getScaleFactor();
|
||||||
|
if (scaleFactor >= 1.1f) {
|
||||||
|
zoomIn();
|
||||||
|
return true;
|
||||||
|
} else if (scaleFactor <= 0.9) {
|
||||||
|
zoomOut();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public ChartView(Context context, boolean chartByDistance) {
|
||||||
super(context);
|
super(context);
|
||||||
|
this.chartByDistance = chartByDistance;
|
||||||
|
|
||||||
series[ELEVATION_SERIES] = new ChartValueSeries(context,
|
series[ELEVATION_SERIES] = new ChartValueSeries(context,
|
||||||
Integer.MIN_VALUE,
|
Integer.MIN_VALUE,
|
||||||
@@ -186,7 +262,7 @@ public class ChartView extends View {
|
|||||||
gridPaint.setAntiAlias(false);
|
gridPaint.setAntiAlias(false);
|
||||||
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
|
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
|
||||||
|
|
||||||
Paint markerPaint = new Paint();
|
markerPaint = new Paint();
|
||||||
markerPaint.setStyle(Style.STROKE);
|
markerPaint.setStyle(Style.STROKE);
|
||||||
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
|
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
|
||||||
markerPaint.setAntiAlias(false);
|
markerPaint.setAntiAlias(false);
|
||||||
@@ -194,12 +270,9 @@ public class ChartView extends View {
|
|||||||
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
||||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||||
|
|
||||||
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
waypointMarker = MarkerUtils.getDefaultPhoto(context);
|
||||||
markerWidth = statisticsMarker.getIntrinsicWidth();
|
markerWidth = waypointMarker.getIntrinsicWidth();
|
||||||
markerHeight = statisticsMarker.getIntrinsicHeight();
|
markerHeight = waypointMarker.getIntrinsicHeight();
|
||||||
statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
|
||||||
|
|
||||||
Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin);
|
|
||||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
scroller = new Scroller(context);
|
scroller = new Scroller(context);
|
||||||
@@ -222,16 +295,6 @@ public class ChartView extends View {
|
|||||||
series[index].setEnabled(enabled);
|
series[index].setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets chart by distance.
|
|
||||||
* It is expected that after changing this value, data will be reloaded.
|
|
||||||
*
|
|
||||||
* @param value true for by distance, false for by time
|
|
||||||
*/
|
|
||||||
public void setChartByDistance(boolean value) {
|
|
||||||
chartByDistance = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets metric units.
|
* Sets metric units.
|
||||||
*
|
*
|
||||||
@@ -264,11 +327,10 @@ public class ChartView extends View {
|
|||||||
*
|
*
|
||||||
* @param dataPoints an array of data points to be added
|
* @param dataPoints an array of data points to be added
|
||||||
*/
|
*/
|
||||||
public void addDataPoints(ArrayList<double[]> dataPoints) {
|
public void addDataPoints(List<double[]> dataPoints) {
|
||||||
synchronized (chartData) {
|
synchronized (chartData) {
|
||||||
chartData.addAll(dataPoints);
|
chartData.addAll(dataPoints);
|
||||||
for (int i = 0; i < dataPoints.size(); i++) {
|
for (double[] dataPoint : dataPoints) {
|
||||||
double[] dataPoint = dataPoints.get(i);
|
|
||||||
xExtremityMonitor.update(dataPoint[0]);
|
xExtremityMonitor.update(dataPoint[0]);
|
||||||
for (int j = 0; j < series.length; j++) {
|
for (int j = 0; j < series.length; j++) {
|
||||||
if (!Double.isNaN(dataPoint[j + 1])) {
|
if (!Double.isNaN(dataPoint[j + 1])) {
|
||||||
@@ -410,79 +472,14 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
if (velocityTracker == null) {
|
boolean isZoom = detectorZoom.onTouchEvent(event);
|
||||||
velocityTracker = VelocityTracker.obtain();
|
boolean isScrollTab = detectorScrollFlingTab.onTouchEvent(event);
|
||||||
}
|
return isZoom || isScrollTab;
|
||||||
velocityTracker.addMovement(event);
|
|
||||||
float x = event.getX();
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
// Stop the fling
|
|
||||||
if (!scroller.isFinished()) {
|
|
||||||
scroller.abortAnimation();
|
|
||||||
}
|
|
||||||
lastMotionEventX = x;
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
if (lastMotionEventX == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Scroll to follow the motion event
|
|
||||||
int deltaX = (int) (lastMotionEventX - x);
|
|
||||||
lastMotionEventX = x;
|
|
||||||
if (deltaX < 0) {
|
|
||||||
if (getScrollX() > 0) {
|
|
||||||
scrollBy(deltaX);
|
|
||||||
}
|
|
||||||
} else if (deltaX > 0) {
|
|
||||||
int availableToScroll = effectiveWidth * (zoomLevel - 1) - getScrollX();
|
|
||||||
if (availableToScroll > 0) {
|
|
||||||
scrollBy(Math.min(availableToScroll, deltaX));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
// Check if the y event is within markerHeight of the marker center
|
|
||||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
|
|
||||||
int minDistance = Integer.MAX_VALUE;
|
|
||||||
Waypoint nearestWaypoint = null;
|
|
||||||
synchronized (waypoints) {
|
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
|
||||||
Waypoint waypoint = waypoints.get(i);
|
|
||||||
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance;
|
|
||||||
nearestWaypoint = waypoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nearestWaypoint != null && minDistance < markerWidth) {
|
|
||||||
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
|
||||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestWaypoint.getId());
|
|
||||||
getContext().startActivity(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VelocityTracker myVelocityTracker = velocityTracker;
|
|
||||||
myVelocityTracker.computeCurrentVelocity(1000);
|
|
||||||
int initialVelocity = (int) myVelocityTracker.getXVelocity();
|
|
||||||
if (Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) {
|
|
||||||
fling(-initialVelocity);
|
|
||||||
}
|
|
||||||
if (velocityTracker != null) {
|
|
||||||
velocityTracker.recycle();
|
|
||||||
velocityTracker = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
updateEffectiveDimensionsIfChanged(
|
updateEffectiveDimensionsIfChanged(View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
||||||
View.MeasureSpec.getSize(widthMeasureSpec), View.MeasureSpec.getSize(heightMeasureSpec));
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,6 +495,7 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
clipToGraphArea(canvas);
|
clipToGraphArea(canvas);
|
||||||
drawDataSeries(canvas);
|
drawDataSeries(canvas);
|
||||||
|
drawWaypoints(canvas);
|
||||||
drawGrid(canvas);
|
drawGrid(canvas);
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
@@ -538,6 +536,33 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the waypoints.
|
||||||
|
*
|
||||||
|
* @param canvas the canvas
|
||||||
|
*/
|
||||||
|
private void drawWaypoints(Canvas canvas) {
|
||||||
|
synchronized (waypoints) {
|
||||||
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
|
final Waypoint waypoint = waypoints.get(i);
|
||||||
|
if (waypoint.getLocation() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double xValue = getWaypointXValue(waypoint);
|
||||||
|
if (xValue > maxX) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
canvas.save();
|
||||||
|
float x = getX(getWaypointXValue(waypoint));
|
||||||
|
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
|
||||||
|
canvas.translate(x - (float) (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
|
||||||
|
|
||||||
|
waypointMarker.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the grid.
|
* Draws the grid.
|
||||||
*
|
*
|
||||||
@@ -545,9 +570,9 @@ public class ChartView extends View {
|
|||||||
*/
|
*/
|
||||||
private void drawGrid(Canvas canvas) {
|
private void drawGrid(Canvas canvas) {
|
||||||
// X axis grid
|
// X axis grid
|
||||||
ArrayList<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
|
List<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
|
||||||
for (int i = 0; i < xAxisMarkerPositions.size(); i++) {
|
for (double position : xAxisMarkerPositions) {
|
||||||
int x = getX(xAxisMarkerPositions.get(i));
|
int x = getX(position);
|
||||||
canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
|
canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
|
||||||
}
|
}
|
||||||
// Y axis grid
|
// Y axis grid
|
||||||
@@ -619,10 +644,10 @@ public class ChartView extends View {
|
|||||||
canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint);
|
canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint);
|
||||||
|
|
||||||
double interval = getXAxisInterval();
|
double interval = getXAxisInterval();
|
||||||
ArrayList<Double> markerPositions = getXAxisMarkerPositions(interval);
|
|
||||||
NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
|
NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
|
||||||
for (int i = 0; i < markerPositions.size(); i++) {
|
|
||||||
drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset);
|
for (double markerPosition : getXAxisMarkerPositions(interval)) {
|
||||||
|
drawXAxisMarker(canvas, markerPosition, numberFormat, spacer + yOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,8 +657,7 @@ public class ChartView extends View {
|
|||||||
private String getXAxisLabel() {
|
private String getXAxisLabel() {
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
return metricUnits ? context.getString(R.string.unit_kilometer)
|
return metricUnits ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile);
|
||||||
: context.getString(R.string.unit_mile);
|
|
||||||
} else {
|
} else {
|
||||||
return context.getString(R.string.description_time);
|
return context.getString(R.string.description_time);
|
||||||
}
|
}
|
||||||
@@ -673,8 +697,8 @@ public class ChartView extends View {
|
|||||||
/**
|
/**
|
||||||
* Gets the x axis marker positions.
|
* Gets the x axis marker positions.
|
||||||
*/
|
*/
|
||||||
private ArrayList<Double> getXAxisMarkerPositions(double interval) {
|
private List<Double> getXAxisMarkerPositions(double interval) {
|
||||||
ArrayList<Double> markers = new ArrayList<>();
|
List<Double> markers = new ArrayList<>();
|
||||||
markers.add(0d);
|
markers.add(0d);
|
||||||
for (int i = 1; i * interval < maxX; i++) {
|
for (int i = 1; i * interval < maxX; i++) {
|
||||||
markers.add(i * interval);
|
markers.add(i * interval);
|
||||||
@@ -733,8 +757,7 @@ public class ChartView extends View {
|
|||||||
* @param yValue the y value
|
* @param yValue the y value
|
||||||
* @return the marker width.
|
* @return the marker width.
|
||||||
*/
|
*/
|
||||||
private float drawYAxisMarker(
|
private float drawYAxisMarker(ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
||||||
ChartValueSeries chartValueSeries, Canvas canvas, int xPosition, int yValue) {
|
|
||||||
String marker = chartValueSeries.formatMarker(yValue);
|
String marker = chartValueSeries.formatMarker(yValue);
|
||||||
Paint paint = chartValueSeries.getMarkerPaint();
|
Paint paint = chartValueSeries.getMarkerPaint();
|
||||||
Rect rect = getRect(paint, marker);
|
Rect rect = getRect(paint, marker);
|
||||||
@@ -785,8 +808,7 @@ public class ChartView extends View {
|
|||||||
private void drawPaths() {
|
private void drawPaths() {
|
||||||
boolean[] hasMoved = new boolean[series.length];
|
boolean[] hasMoved = new boolean[series.length];
|
||||||
|
|
||||||
for (int i = 0; i < chartData.size(); i++) {
|
for (double[] dataPoint : chartData) {
|
||||||
double[] dataPoint = chartData.get(i);
|
|
||||||
for (int j = 0; j < series.length; j++) {
|
for (int j = 0; j < series.length; j++) {
|
||||||
double value = dataPoint[j + 1];
|
double value = dataPoint[j + 1];
|
||||||
if (Double.isNaN(value)) {
|
if (Double.isNaN(value)) {
|
||||||
@@ -869,8 +891,7 @@ public class ChartView extends View {
|
|||||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||||
// border + x axis marker + spacer + .5 x axis label
|
// border + x axis marker + spacer + .5 x axis label
|
||||||
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer
|
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer + (xAxisLabelRect.height() / 2));
|
||||||
+ (xAxisLabelRect.height() / 2));
|
|
||||||
rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer);
|
rightBorder = (int) (density * BORDER + xAxisLabelRect.width() + spacer);
|
||||||
updateEffectiveDimensions();
|
updateEffectiveDimensions();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link SensorDataSetLocation}.
|
||||||
|
* An implementation can create new instances or reuse existing instances for optimization.
|
||||||
|
*/
|
||||||
|
public class LocationFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link LocationFactory} which creates a location each time.
|
||||||
|
*/
|
||||||
|
public static LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory();
|
||||||
|
|
||||||
|
public Location createLocation() {
|
||||||
|
return new SensorDataSetLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
|
||||||
|
*/
|
||||||
|
public interface LocationIterator extends Iterator<Location>, AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the most recently retrieved track point id by {@link #next()}.
|
||||||
|
*/
|
||||||
|
long getLocationId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the iterator.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
}
|
||||||
@@ -28,11 +28,6 @@ public class SensorDataSetLocation extends Location {
|
|||||||
|
|
||||||
private SensorDataSet sensorDataSet = null;
|
private SensorDataSet sensorDataSet = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of this location from the provider.
|
|
||||||
*/
|
|
||||||
private int id = -1;
|
|
||||||
|
|
||||||
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
|
public SensorDataSetLocation(Location location, SensorDataSet sensorDataSet) {
|
||||||
super(location);
|
super(location);
|
||||||
this.sensorDataSet = sensorDataSet;
|
this.sensorDataSet = sensorDataSet;
|
||||||
@@ -50,17 +45,8 @@ public class SensorDataSetLocation extends Location {
|
|||||||
this.sensorDataSet = sensorDataSet;
|
this.sensorDataSet = sensorDataSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
sensorDataSet = null;
|
sensorDataSet = null;
|
||||||
id = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtilsImpl(this);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
|
||||||
long[] trackIds = parseURI(uri);
|
long[] trackIds = parseURI(uri);
|
||||||
final Track[] tracks = new Track[trackIds.length];
|
final Track[] tracks = new Track[trackIds.length];
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import java.util.EnumSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
@@ -41,17 +40,24 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
*/
|
*/
|
||||||
public class TrackDataHub implements DataSourceListener {
|
public class TrackDataHub implements DataSourceListener {
|
||||||
|
|
||||||
|
public synchronized static TrackDataHub newInstance(Context context) {
|
||||||
|
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target number of track points displayed by the map overlay.
|
* Target number of track points displayed by the map overlay.
|
||||||
* We may display more than this number of points.
|
* We may display more than this number of points.
|
||||||
*/
|
*/
|
||||||
static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
@Deprecated
|
||||||
|
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of waypoints to displayed.
|
* Maximum number of waypoints to displayed.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@Deprecated
|
||||||
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
|
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
|
||||||
|
|
||||||
private static final String TAG = TrackDataHub.class.getSimpleName();
|
private static final String TAG = TrackDataHub.class.getSimpleName();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@@ -96,11 +102,6 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
resetSamplingState();
|
resetSamplingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static TrackDataHub newInstance(Context context) {
|
|
||||||
return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get(
|
|
||||||
context), TARGET_DISPLAYED_TRACK_POINTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (started) {
|
if (started) {
|
||||||
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
||||||
@@ -254,13 +255,13 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
@@ -270,7 +271,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_rate_key))) {
|
if (PreferencesUtils.isKey(context, R.string.stats_rate_key, key)) {
|
||||||
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
@@ -280,8 +281,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
if (trackDataListener.onRecordingGpsAccuracy(recordingGpsAccuracy)) {
|
||||||
@@ -290,8 +291,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||||
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
for (TrackDataListener trackDataListener : trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
|
||||||
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
|
if (trackDataListener.onRecordingDistanceIntervalChanged(recordingDistanceInterval)) {
|
||||||
@@ -398,8 +399,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
trackDataListener.clearWaypoints();
|
trackDataListener.clearWaypoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Cursor cursor = contentProviderUtils.getWaypointCursor(
|
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
|
||||||
selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
do {
|
do {
|
||||||
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
|
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
|
||||||
@@ -448,7 +448,7 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
int samplingFrequency = -1;
|
int samplingFrequency = -1;
|
||||||
boolean includeNextPoint = false;
|
boolean includeNextPoint = false;
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
|
|
||||||
while (locationIterator.hasNext()) {
|
while (locationIterator.hasNext()) {
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
@@ -468,12 +468,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LocationUtils.isValidLocation(location)) {
|
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
|
||||||
// TODO: also include the last valid point before a split
|
|
||||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
|
||||||
trackDataListener.onSegmentSplit(location);
|
|
||||||
includeNextPoint = true;
|
includeNextPoint = true;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Also include the last point if the selected track is not recording.
|
// Also include the last point if the selected track is not recording.
|
||||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||||
|
|||||||
@@ -51,14 +51,9 @@ public interface TrackDataListener {
|
|||||||
*/
|
*/
|
||||||
void onSampledOutTrackPoint(Location location);
|
void onSampledOutTrackPoint(Location location);
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an invalid track point representing a segment split is read.
|
|
||||||
*/
|
|
||||||
void onSegmentSplit(Location location);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when finish sending new track points.
|
* Called when finish sending new track points.
|
||||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)}, {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit(Location)}.
|
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
|
||||||
*/
|
*/
|
||||||
void onNewTrackPointsDone();
|
void onNewTrackPointsDone();
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ public final class Waypoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum WaypointType {
|
public enum WaypointType {
|
||||||
WAYPOINT, STATISTICS
|
WAYPOINT,
|
||||||
|
STATISTICS //Always the first waypoint; only one can exist.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ public class WaypointCreationRequest {
|
|||||||
|
|
||||||
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(WaypointType.WAYPOINT, false);
|
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(WaypointType.WAYPOINT, false);
|
||||||
|
|
||||||
public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest(WaypointType.STATISTICS, false);
|
@Deprecated // TODO Do not create per
|
||||||
|
|
||||||
public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest(WaypointType.STATISTICS, true);
|
public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest(WaypointType.STATISTICS, true);
|
||||||
|
|
||||||
private WaypointType type;
|
private WaypointType type;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ public final class SensorDataSet {
|
|||||||
|
|
||||||
public static final float DATA_UNAVAILABLE = Float.NaN;
|
public static final float DATA_UNAVAILABLE = Float.NaN;
|
||||||
|
|
||||||
//TODO It might be necessary to consider: sensor if sensor was connected as well.
|
//TODO It might be necessary to consider: if sensor was connected as well.
|
||||||
public String sensorName;
|
private String sensorName;
|
||||||
public String sensorAddress;
|
private String sensorAddress;
|
||||||
private float heartRate;
|
private float heartRate;
|
||||||
private float cadence;
|
private float cadence;
|
||||||
private float power;
|
private float power;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
public class ChartDistanceFragment extends ChartFragment {
|
||||||
|
|
||||||
|
public static final String CHART_FRAGMENT_TAG = ChartDistanceFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
public ChartDistanceFragment() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,19 +22,20 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.widget.ZoomControls;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.ChartView;
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||||
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
@@ -54,11 +55,11 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ChartFragment extends Fragment implements TrackDataListener {
|
public abstract class ChartFragment extends Fragment implements TrackDataListener {
|
||||||
|
|
||||||
public static final String CHART_FRAGMENT_TAG = "chartFragment";
|
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
|
||||||
|
|
||||||
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
|
private final List<double[]> pendingPoints = new ArrayList<>();
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
|
|
||||||
@@ -68,18 +69,17 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
private boolean metricUnits = true;
|
private boolean metricUnits = true;
|
||||||
private boolean reportSpeed = true;
|
private boolean reportSpeed = true;
|
||||||
private int recordingDistanceInterval = PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT;
|
private int recordingDistanceInterval;
|
||||||
|
|
||||||
// Modes of operation
|
// Modes of operation
|
||||||
private boolean chartByDistance = true;
|
private boolean chartByDistance;
|
||||||
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
|
private boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
private ChartView chartView;
|
private ChartView chartView;
|
||||||
private ZoomControls zoomControls;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable that will enable/disable zoom controls and orange pointer as appropriate and redraw.
|
* A runnable that will set the orange pointer as appropriate and redraw.
|
||||||
*/
|
*/
|
||||||
private final Runnable updateChart = new Runnable() {
|
private final Runnable updateChart = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -88,46 +88,34 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
chartView.setShowPointer(isSelectedTrackRecording());
|
chartView.setShowPointer(isSelectedTrackRecording());
|
||||||
chartView.invalidate();
|
chartView.invalidate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ChartFragment(boolean chartByDistance) {
|
||||||
|
this.chartByDistance = chartByDistance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
/*
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
||||||
* Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
|
||||||
*/
|
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
||||||
chartView = new ChartView(getActivity());
|
chartView = new ChartView(getContext(), chartByDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.chart, container, false);
|
return inflater.inflate(R.layout.chart, container, false);
|
||||||
zoomControls = view.findViewById(R.id.chart_zoom_controls);
|
|
||||||
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
zoomIn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
zoomOut();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
|
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
|
||||||
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||||
layout.addView(chartView, layoutParams);
|
layout.addView(chartView, layoutParams);
|
||||||
}
|
}
|
||||||
@@ -149,10 +137,24 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
ViewGroup layout = getActivity().findViewById(R.id.chart_view_layout);
|
ViewGroup layout = getView().findViewById(R.id.chart_view_layout);
|
||||||
layout.removeView(chartView);
|
layout.removeView(chartView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
chartByDistance = savedInstanceState.getBoolean(STATE_CHART_VIEW_BY_DISTANCE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(Track track) {
|
public void onTrackUpdated(Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -197,13 +199,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSegmentSplit(Location location) {
|
|
||||||
if (isResumed()) {
|
|
||||||
fillDataPoint(location, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewTrackPointsDone() {
|
public void onNewTrackPointsDone() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -263,10 +258,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
reportSpeed = speed;
|
reportSpeed = speed;
|
||||||
chartView.setReportSpeed(reportSpeed);
|
chartView.setReportSpeed(reportSpeed);
|
||||||
boolean chartShowSpeed = PreferencesUtils.getBoolean(
|
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
|
||||||
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
|
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
|
||||||
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
|
|
||||||
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -303,32 +296,14 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
*/
|
*/
|
||||||
private void checkChartSettings() {
|
private void checkChartSettings() {
|
||||||
boolean needUpdate = false;
|
boolean needUpdate = false;
|
||||||
if (chartByDistance != PreferencesUtils.isChartByDistance(getActivity())) {
|
|
||||||
chartByDistance = !chartByDistance;
|
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
|
||||||
chartView.setChartByDistance(chartByDistance);
|
|
||||||
reloadTrackDataHub();
|
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
|
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean chartShowSpeed = PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
|
|
||||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_power_key, PreferencesUtils.CHART_SHOW_POWER_DEFAULT))) {
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_cadence_key, PreferencesUtils.CHART_SHOW_CADENCE_DEFAULT))) {
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.getBoolean(getActivity(), R.string.chart_show_heart_rate_key, PreferencesUtils.CHART_SHOW_HEART_RATE_DEFAULT))) {
|
|
||||||
needUpdate = true;
|
|
||||||
}
|
|
||||||
if (needUpdate) {
|
if (needUpdate) {
|
||||||
chartView.postInvalidate();
|
chartView.postInvalidate();
|
||||||
}
|
}
|
||||||
@@ -389,18 +364,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void zoomIn() {
|
|
||||||
chartView.zoomIn();
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void zoomOut() {
|
|
||||||
chartView.zoomOut();
|
|
||||||
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
|
|
||||||
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a runnable on the UI thread if possible.
|
* Runs a runnable on the UI thread if possible.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
public class ChartTimeFragment extends ChartFragment {
|
||||||
|
|
||||||
|
public static final String CHART_FRAGMENT_TAG = ChartTimeFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
public ChartTimeFragment() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.dennisguse.opentracks.fragments;
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -79,19 +78,6 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Only used by obsolete Androidx preferences. Remove when onAttach(Context) is called.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onAttach(@NonNull Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
try {
|
|
||||||
chooseActivityTypeCaller = (ChooseActivityTypeCaller) activity;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException(activity + " must implement " + ChooseActivityTypeCaller.class.getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
|||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(fragmentActivity);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||||
for (long markerId : markerIds) {
|
for (long markerId : markerIds) {
|
||||||
contentProviderUtils.deleteWaypoint(fragmentActivity,
|
contentProviderUtils.deleteWaypoint(fragmentActivity,
|
||||||
markerId, new DescriptionGeneratorImpl(fragmentActivity));
|
markerId, new DescriptionGeneratorImpl(fragmentActivity));
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.fragments;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A DialogFragment to configure frequency.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public class FrequencyDialogFragment extends DialogFragment {
|
|
||||||
|
|
||||||
private static final String FREQUENCY_DIALOG_TAG = "frequencyDialog";
|
|
||||||
private int preferenceId;
|
|
||||||
private int defaultValue;
|
|
||||||
private int titleId;
|
|
||||||
|
|
||||||
public FrequencyDialogFragment(int preferenceId, int defaultValue, int titleId) {
|
|
||||||
this.preferenceId = preferenceId;
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
this.titleId = titleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showDialog(FragmentManager fragmentManager, int preferenceId, int defaultValue, int titleId) {
|
|
||||||
new FrequencyDialogFragment(preferenceId, defaultValue, titleId).show(fragmentManager, FREQUENCY_DIALOG_TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
FragmentActivity fragmentActivity = getActivity();
|
|
||||||
|
|
||||||
int frequencyValue = PreferencesUtils.getInt(fragmentActivity, preferenceId, defaultValue);
|
|
||||||
|
|
||||||
return new AlertDialog.Builder(fragmentActivity).setPositiveButton(
|
|
||||||
R.string.generic_ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
int listIndex = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
|
||||||
PreferencesUtils.setInt(getActivity(), preferenceId, getFrequencyValue(listIndex));
|
|
||||||
}
|
|
||||||
}).setSingleChoiceItems(
|
|
||||||
getFrequencyDisplayOptions(fragmentActivity), getListIndex(frequencyValue), null)
|
|
||||||
.setTitle(titleId).create();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the frequency display options.
|
|
||||||
*/
|
|
||||||
private String[] getFrequencyDisplayOptions(FragmentActivity fragmentActivity) {
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(fragmentActivity);
|
|
||||||
return StringUtils.getFrequencyOptions(fragmentActivity, metricUnits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list index for a frequency value.
|
|
||||||
* Returns 0 if the value is not on the list.
|
|
||||||
*/
|
|
||||||
private int getListIndex(int frequencyValue) {
|
|
||||||
String[] values = getResources().getStringArray(R.array.frequency_values);
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
if (frequencyValue == Integer.parseInt(values[i])) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the frequency value from a list index.
|
|
||||||
*
|
|
||||||
* @param listIndex the list index
|
|
||||||
*/
|
|
||||||
private int getFrequencyValue(int listIndex) {
|
|
||||||
String[] values = getResources().getStringArray(R.array.frequency_values);
|
|
||||||
return Integer.parseInt(values[listIndex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,12 +41,10 @@ import androidx.fragment.app.FragmentActivity;
|
|||||||
import de.dennisguse.opentracks.MarkerEditActivity;
|
import de.dennisguse.opentracks.MarkerEditActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
@@ -62,7 +60,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND;
|
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND;
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private ImageView photo;
|
private ImageView photoView;
|
||||||
private ImageView textGradient;
|
private ImageView textGradient;
|
||||||
private LinearLayout waypointInfo;
|
private LinearLayout waypointInfo;
|
||||||
private Waypoint waypoint;
|
private Waypoint waypoint;
|
||||||
@@ -110,7 +108,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
getFragmentManager().popBackStack();
|
getFragmentManager().popBackStack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(getActivity());
|
contentProviderUtils = new ContentProviderUtils(getActivity());
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
@@ -119,11 +117,11 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
|
View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
|
||||||
|
|
||||||
photo = view.findViewById(R.id.marker_detail_waypoint_photo);
|
photoView = view.findViewById(R.id.marker_detail_waypoint_photo);
|
||||||
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||||
|
|
||||||
photo.setOnClickListener(new View.OnClickListener() {
|
photoView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
handler.removeCallbacks(hideText);
|
handler.removeCallbacks(hideText);
|
||||||
@@ -156,11 +154,8 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void setMenuVisibility(boolean menuVisible) {
|
public void setMenuVisibility(boolean menuVisible) {
|
||||||
super.setMenuVisibility(menuVisible);
|
super.setMenuVisibility(menuVisible);
|
||||||
/*
|
// View pager caches the neighboring fragments in the resumed state.
|
||||||
* View pager caches the neighboring fragments in the resumed state. If
|
// If becoming visible from the resumed state, update the UI to display the text above the image.
|
||||||
* becoming visible from the resumed state, update the UI to display the
|
|
||||||
* text above the image.
|
|
||||||
*/
|
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
if (menuVisible) {
|
if (menuVisible) {
|
||||||
updateUi();
|
updateUi();
|
||||||
@@ -214,27 +209,14 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the UI.
|
|
||||||
*/
|
|
||||||
private void updateUi() {
|
private void updateUi() {
|
||||||
View waypointView = getView().findViewById(R.id.marker_detail_waypoint);
|
|
||||||
View statisticsView = getView().findViewById(R.id.marker_detail_statistics);
|
|
||||||
|
|
||||||
boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT;
|
|
||||||
waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE);
|
|
||||||
statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
if (isWaypoint) {
|
|
||||||
boolean hasPhoto = waypoint.hasPhoto();
|
boolean hasPhoto = waypoint.hasPhoto();
|
||||||
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
|
||||||
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
|
||||||
waypointInfo.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (hasPhoto) {
|
if (hasPhoto) {
|
||||||
handler.removeCallbacks(hideText);
|
handler.removeCallbacks(hideText);
|
||||||
photo.setImageURI(waypoint.getPhotoURI());
|
photoView.setImageURI(waypoint.getPhotoURI());
|
||||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||||
|
} else {
|
||||||
|
photoView.setImageResource(MarkerUtils.ICON_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
setName(R.id.marker_detail_waypoint_name, hasPhoto);
|
setName(R.id.marker_detail_waypoint_name, hasPhoto);
|
||||||
@@ -246,15 +228,6 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
|
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
|
||||||
|
|
||||||
setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
|
setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
|
||||||
} else {
|
|
||||||
setName(R.id.marker_detail_statistics_name, false);
|
|
||||||
|
|
||||||
setLocation(R.id.marker_detail_statistics_location, false);
|
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(waypoint.getTrackId());
|
|
||||||
StatsUtils.setTripStatisticsValues(getActivity(), waypoint.getTripStatistics(), null);
|
|
||||||
StatsUtils.setLocationValues(getActivity(), waypoint.getLocation(), false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setName(int resId, boolean addShadow) {
|
private void setName(int resId, boolean addShadow) {
|
||||||
|
|||||||
@@ -20,16 +20,18 @@ import android.location.Location;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
@@ -43,10 +45,11 @@ import de.dennisguse.opentracks.content.Waypoint;
|
|||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
import de.dennisguse.opentracks.util.LocationUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
@@ -60,38 +63,136 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND;
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
private Handler handlerUpdateUI;
|
private Handler handlerUpdateUI;
|
||||||
|
|
||||||
|
//TODO Initialize immediately and remove in onDestroy()
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
|
|
||||||
private Location lastLocation = null;
|
private Location lastLocation = null;
|
||||||
private TripStatistics lastTripStatistics = null;
|
private TripStatistics lastTripStatistics = null;
|
||||||
private SensorDataSet sensorDataSet = null;
|
|
||||||
|
|
||||||
private final Runnable updateUIeachSecond = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (isResumed() && isSelectedTrackRecording()) {
|
|
||||||
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
|
||||||
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
|
|
||||||
updateSensorDataUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private String category = "";
|
private String category = "";
|
||||||
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
|
private int recordingGpsAccuracy;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.stats, container, false);
|
return inflater.inflate(R.layout.stats, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Views */
|
||||||
|
private View sensorContainerView;
|
||||||
|
private TextView heartRateValueView;
|
||||||
|
private TextView heartRateSensorView;
|
||||||
|
|
||||||
|
private TextView totalTimeValueView;
|
||||||
|
private final Runnable updateUIeachSecond = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (isResumed() && isSelectedTrackRecording()) {
|
||||||
|
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
||||||
|
updateTotalTime();
|
||||||
|
updateSensorDataUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private TextView distanceValue;
|
||||||
|
private TextView distanceUnit;
|
||||||
|
private View activityLabel;
|
||||||
|
private Spinner activitySpinner;
|
||||||
|
private TextView movingTimeValue;
|
||||||
|
private TextView speedAvgLabel;
|
||||||
|
private TextView speedAvgValue;
|
||||||
|
private TextView speedAvgUnit;
|
||||||
|
private TextView speedMaxLabel;
|
||||||
|
private TextView speedMaxValue;
|
||||||
|
private TextView speedMaxUnit;
|
||||||
|
private TextView speedMovingLabel;
|
||||||
|
private TextView speedMovingValue;
|
||||||
|
private TextView speedMovingUnit;
|
||||||
|
private View gradeElevationSeparator;
|
||||||
|
private View gradeElevationContainer;
|
||||||
|
private TextView elevationMinValue;
|
||||||
|
private TextView elevationMinUnit;
|
||||||
|
private TextView elevationMaxValue;
|
||||||
|
private TextView elevationMaxUnit;
|
||||||
|
private View speedContainer;
|
||||||
|
private TextView speedLabel;
|
||||||
|
private TextView speedValue;
|
||||||
|
private TextView speedUnit;
|
||||||
|
private View elevationContainer;
|
||||||
|
private TextView elevationValue;
|
||||||
|
private TextView elevationUnit;
|
||||||
|
private View coordinateSeparator;
|
||||||
|
private View coordinateContainer;
|
||||||
|
private TextView latitudeValue;
|
||||||
|
private TextView longitudeValue;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
sensorContainerView = view.findViewById(R.id.stats_sensor_container);
|
||||||
|
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
|
||||||
|
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||||
|
|
||||||
|
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
|
||||||
|
|
||||||
|
distanceValue = view.findViewById(R.id.stats_distance_value);
|
||||||
|
distanceUnit = view.findViewById(R.id.stats_distance_unit);
|
||||||
|
|
||||||
|
activityLabel = view.findViewById(R.id.stats_activity_type_label);
|
||||||
|
activitySpinner = view.findViewById(R.id.stats_activity_type_icon);
|
||||||
|
|
||||||
|
movingTimeValue = view.findViewById(R.id.stats_moving_time_value);
|
||||||
|
|
||||||
|
speedAvgLabel = view.findViewById(R.id.stats_average_speed_label);
|
||||||
|
speedAvgValue = view.findViewById(R.id.stats_average_speed_value);
|
||||||
|
speedAvgUnit = view.findViewById(R.id.stats_average_speed_unit);
|
||||||
|
|
||||||
|
speedMaxLabel = view.findViewById(R.id.stats_max_speed_label);
|
||||||
|
speedMaxValue = view.findViewById(R.id.stats_max_speed_value);
|
||||||
|
speedMaxUnit = view.findViewById(R.id.stats_max_speed_unit);
|
||||||
|
|
||||||
|
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
||||||
|
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||||
|
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||||
|
|
||||||
|
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||||
|
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||||
|
|
||||||
|
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
|
||||||
|
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
|
||||||
|
|
||||||
|
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
|
||||||
|
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
|
||||||
|
|
||||||
|
speedContainer = view.findViewById(R.id.stats_speed);
|
||||||
|
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||||
|
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||||
|
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||||
|
|
||||||
|
elevationContainer = view.findViewById(R.id.stats_elevation);
|
||||||
|
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||||
|
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||||
|
|
||||||
|
coordinateSeparator = view.findViewById(R.id.stats_coordinate_separator);
|
||||||
|
coordinateContainer = view.findViewById(R.id.stats_coordinate_container);
|
||||||
|
|
||||||
|
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
||||||
|
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
recordingGpsAccuracy = Integer.parseInt(getContext().getResources().getString(R.string.recording_gps_accuracy_default));
|
||||||
|
|
||||||
handlerUpdateUI = new Handler();
|
handlerUpdateUI = new Handler();
|
||||||
|
|
||||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||||
@@ -124,11 +225,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||||
trackRecordingServiceConnection.startConnection(getContext());
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
|
|
||||||
updateUi(getActivity());
|
|
||||||
if (isSelectedTrackRecording()) {
|
|
||||||
handlerUpdateUI.post(updateUIeachSecond);
|
handlerUpdateUI.post(updateUIeachSecond);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
@@ -140,10 +238,66 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
if (trackRecordingServiceConnection != null) {
|
||||||
trackRecordingServiceConnection.unbind();
|
trackRecordingServiceConnection.unbind();
|
||||||
|
}
|
||||||
trackRecordingServiceConnection = null;
|
trackRecordingServiceConnection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
sensorContainerView = null;
|
||||||
|
heartRateValueView = null;
|
||||||
|
heartRateSensorView = null;
|
||||||
|
|
||||||
|
totalTimeValueView = null;
|
||||||
|
|
||||||
|
distanceValue = null;
|
||||||
|
distanceUnit = null;
|
||||||
|
|
||||||
|
activityLabel = null;
|
||||||
|
activitySpinner = null;
|
||||||
|
|
||||||
|
movingTimeValue = null;
|
||||||
|
|
||||||
|
speedAvgLabel = null;
|
||||||
|
speedAvgValue = null;
|
||||||
|
speedAvgUnit = null;
|
||||||
|
|
||||||
|
speedMaxLabel = null;
|
||||||
|
speedMaxValue = null;
|
||||||
|
speedMaxUnit = null;
|
||||||
|
|
||||||
|
speedMovingLabel = null;
|
||||||
|
speedMovingValue = null;
|
||||||
|
speedMovingUnit = null;
|
||||||
|
|
||||||
|
gradeElevationSeparator = null;
|
||||||
|
gradeElevationContainer = null;
|
||||||
|
|
||||||
|
elevationMinValue = null;
|
||||||
|
elevationMinUnit = null;
|
||||||
|
|
||||||
|
elevationMaxValue = null;
|
||||||
|
elevationMaxUnit = null;
|
||||||
|
|
||||||
|
speedContainer = null;
|
||||||
|
speedLabel = null;
|
||||||
|
speedValue = null;
|
||||||
|
speedUnit = null;
|
||||||
|
|
||||||
|
elevationContainer = null;
|
||||||
|
elevationValue = null;
|
||||||
|
elevationUnit = null;
|
||||||
|
|
||||||
|
coordinateSeparator = null;
|
||||||
|
coordinateContainer = null;
|
||||||
|
|
||||||
|
latitudeValue = null;
|
||||||
|
longitudeValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(final Track track) {
|
public void onTrackUpdated(final Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -153,7 +307,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
||||||
category = track != null ? track.getCategory() : "";
|
category = track != null ? track.getCategory() : "";
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -175,11 +329,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
lastLocation = location;
|
lastLocation = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSegmentSplit(Location location) {
|
|
||||||
// We don't care.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewTrackPointsDone() {
|
public void onNewTrackPointsDone() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -199,7 +348,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
lastLocation = null;
|
lastLocation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StatsUtils.setLocationValues(getActivity(), lastLocation, isSelectedTrackRecording());
|
setLocationValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -228,7 +377,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -243,7 +392,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -299,29 +448,196 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
|
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
updateStats();
|
||||||
|
setLocationValues();
|
||||||
|
updateSensorDataUI();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
|
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
|
||||||
*/
|
*/
|
||||||
private void updateSensorDataUI() {
|
private void updateSensorDataUI() {
|
||||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
|
|
||||||
|
SensorDataSet sensorDataSet = null;
|
||||||
if (trackRecordingService == null) {
|
if (trackRecordingService == null) {
|
||||||
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
||||||
sensorDataSet = null;
|
|
||||||
} else {
|
} else {
|
||||||
//TODO sensorState = trackRecordingService.getSensorState();
|
//TODO sensorState = trackRecordingService.getSensorState();
|
||||||
sensorDataSet = trackRecordingService.getSensorData();
|
sensorDataSet = trackRecordingService.getSensorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatsUtils.setSensorData(getActivity(), sensorDataSet, isSelectedTrackRecording());
|
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||||
* Updates the UI.
|
// heart rate
|
||||||
*/
|
int isVisible = View.VISIBLE;
|
||||||
private void updateUi(FragmentActivity activity) {
|
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext())) {
|
||||||
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
|
isVisible = View.INVISIBLE;
|
||||||
StatsUtils.setTripStatisticsValues(activity, lastTripStatistics, trackIconValue);
|
}
|
||||||
StatsUtils.setLocationValues(activity, lastLocation, isSelectedTrackRecording());
|
sensorContainerView.setVisibility(isVisible);
|
||||||
updateSensorDataUI();
|
|
||||||
|
if (isRecording) {
|
||||||
|
String heartRate = getContext().getString(R.string.value_unknown);
|
||||||
|
String sensorName = getContext().getString(R.string.value_unknown);
|
||||||
|
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
|
||||||
|
sensorName = sensorDataSet.getSensorName();
|
||||||
|
if (sensorDataSet.hasHeartRate()) {
|
||||||
|
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heartRateSensorView.setText(sensorName);
|
||||||
|
heartRateValueView.setText(heartRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStats() {
|
||||||
|
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||||
|
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||||
|
boolean isRecording = isSelectedTrackRecording();
|
||||||
|
|
||||||
|
// Set total distance
|
||||||
|
{
|
||||||
|
double totalDistance = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getTotalDistance();
|
||||||
|
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||||
|
|
||||||
|
distanceValue.setText(parts.first);
|
||||||
|
distanceUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set activity type
|
||||||
|
{
|
||||||
|
activityLabel.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
activitySpinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||||
|
activitySpinner.setEnabled(isRecording);
|
||||||
|
if (trackIconValue != null) {
|
||||||
|
TrackIconUtils.setIconSpinner(activitySpinner, trackIconValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set time
|
||||||
|
if (lastTripStatistics != null) {
|
||||||
|
movingTimeValue.setText(StringUtils.formatElapsedTime(lastTripStatistics.getMovingTime()));
|
||||||
|
updateTotalTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set average speed/pace
|
||||||
|
{
|
||||||
|
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageSpeed() : Double.NaN;
|
||||||
|
speedAvgLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedAvgValue.setText(parts.first);
|
||||||
|
speedAvgUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set max speed/pace
|
||||||
|
{
|
||||||
|
double speed = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxSpeed();
|
||||||
|
|
||||||
|
speedMaxLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedMaxValue.setText(parts.first);
|
||||||
|
speedMaxUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set moving speed/pace
|
||||||
|
{
|
||||||
|
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||||
|
|
||||||
|
speedMovingLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedMovingValue.setText(parts.first);
|
||||||
|
speedMovingUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set elevation
|
||||||
|
{
|
||||||
|
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||||
|
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
if (showElevation) {
|
||||||
|
{
|
||||||
|
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||||
|
|
||||||
|
elevationMinValue.setText(parts.first);
|
||||||
|
elevationMinUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||||
|
|
||||||
|
elevationMaxValue.setText(parts.first);
|
||||||
|
elevationMaxUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTotalTime() {
|
||||||
|
long totalTime;
|
||||||
|
if (isSelectedTrackRecording()) {
|
||||||
|
totalTime = System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime();
|
||||||
|
} else {
|
||||||
|
totalTime = lastTripStatistics.getTotalTime();
|
||||||
|
}
|
||||||
|
totalTimeValueView.setText(StringUtils.formatElapsedTime(totalTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLocationValues() {
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||||
|
boolean isRecording = isSelectedTrackRecording();
|
||||||
|
|
||||||
|
// Set speed/pace
|
||||||
|
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
if (isRecording) {
|
||||||
|
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||||
|
|
||||||
|
double speed = lastLocation != null && lastLocation.hasSpeed() ? lastLocation.getSpeed() : Double.NaN;
|
||||||
|
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||||
|
speedValue.setText(parts.first);
|
||||||
|
speedUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set elevation
|
||||||
|
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||||
|
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
if (showGradeElevation) {
|
||||||
|
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||||
|
|
||||||
|
elevationValue.setText(parts.first);
|
||||||
|
elevationUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set coordinate
|
||||||
|
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||||
|
|
||||||
|
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||||
|
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||||
|
if (showCoordinate) {
|
||||||
|
double latitude = lastLocation != null ? lastLocation.getLatitude() : Double.NaN;
|
||||||
|
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||||
|
latitudeValue.setText(latitudeText);
|
||||||
|
|
||||||
|
double longitude = lastLocation != null ? lastLocation.getLongitude() : Double.NaN;
|
||||||
|
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
|
||||||
|
longitudeValue.setText(longitudeText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ public enum TrackFileFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
@@ -173,10 +173,12 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
||||||
|
|
||||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1);
|
|
||||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||||
|
|
||||||
|
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,12 +186,10 @@ public enum TrackFileFormat {
|
|||||||
*/
|
*/
|
||||||
public abstract String getMimeType();
|
public abstract String getMimeType();
|
||||||
|
|
||||||
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
|
||||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||||
|
|
||||||
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -103,16 +103,8 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
}
|
}
|
||||||
|
|
||||||
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||||
|
|
||||||
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
|
|
||||||
// Object retained = getLastNonConfigurationInstance();
|
|
||||||
// if (retained instanceof ExportAsyncTask) {
|
|
||||||
// exportAsyncTask = (ExportAsyncTask) retained;
|
|
||||||
// exportAsyncTask.setActivity(this);
|
|
||||||
// } else {
|
|
||||||
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
|
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
|
||||||
exportAsyncTask.execute();
|
exportAsyncTask.execute();
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.TracksColumns;
|
import de.dennisguse.opentracks.content.TracksColumns;
|
||||||
@@ -69,7 +68,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
context = exportActivity.getApplicationContext();
|
context = exportActivity.getApplicationContext();
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
completed = false;
|
completed = false;
|
||||||
processedTrackCount = 0;
|
processedTrackCount = 0;
|
||||||
@@ -100,7 +99,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
try {
|
try {
|
||||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||||
boolean isRecording = PreferencesUtils.isRecording(exportActivity);
|
boolean isRecording = PreferencesUtils.isRecording(exportActivity);
|
||||||
boolean isPaused = PreferencesUtils.getBoolean(exportActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(exportActivity);
|
||||||
// Get the wake lock if not recording or paused
|
// Get the wake lock if not recording or paused
|
||||||
if (!isRecording || isPaused) {
|
if (!isRecording || isPaused) {
|
||||||
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import androidx.annotation.NonNull;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
@@ -63,7 +64,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean writeTrack(Context context, @NonNull OutputStream outputStream) {
|
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
|
||||||
try {
|
try {
|
||||||
trackWriter.prepare(outputStream);
|
trackWriter.prepare(outputStream);
|
||||||
trackWriter.writeHeader(tracks);
|
trackWriter.writeHeader(tracks);
|
||||||
@@ -209,7 +210,7 @@ public class FileTrackExporter implements TrackExporter {
|
|||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory {
|
private class TrackWriterLocationFactory extends LocationFactory {
|
||||||
Location currentLocation;
|
Location currentLocation;
|
||||||
Location lastLocation;
|
Location lastLocation;
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
this.exportSensorData = exportSensorData;
|
this.exportSensorData = exportSensorData;
|
||||||
this.exportPhotos = exportPhotos;
|
this.exportPhotos = exportPhotos;
|
||||||
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
|
this.descriptionGenerator = new DescriptionGeneratorImpl(context);
|
||||||
this.contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
this.contentProviderUtils = new ContentProviderUtils(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package de.dennisguse.opentracks.io.file.exporter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@@ -37,5 +36,5 @@ public interface TrackExporter {
|
|||||||
* @param context the context
|
* @param context the context
|
||||||
* @param outputStream the output stream
|
* @param outputStream the output stream
|
||||||
*/
|
*/
|
||||||
boolean writeTrack(@Nullable Context context, @NonNull OutputStream outputStream);
|
boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ import javax.xml.parsers.SAXParserFactory;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||||
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||||
@@ -102,7 +103,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.importTrackId = importTrackId;
|
this.importTrackId = importTrackId;
|
||||||
this.contentProviderUtils = contentProviderUtils;
|
this.contentProviderUtils = contentProviderUtils;
|
||||||
this.recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||||
trackIds = new ArrayList<>();
|
trackIds = new ArrayList<>();
|
||||||
waypoints = new ArrayList<>();
|
waypoints = new ArrayList<>();
|
||||||
}
|
}
|
||||||
@@ -165,7 +166,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
|||||||
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||||
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (waypoint == null) {
|
if (waypoint == null) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
* @param context the context
|
* @param context the context
|
||||||
*/
|
*/
|
||||||
public GpxFileTrackImporter(Context context) {
|
public GpxFileTrackImporter(Context context) {
|
||||||
this(context, ContentProviderUtils.Factory.get(context));
|
this(context, new ContentProviderUtils(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
@@ -46,7 +45,9 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final String path;
|
private final String path;
|
||||||
private final Context context;
|
// TODO Can be removed, or?
|
||||||
|
private Context context;
|
||||||
|
// TODO Use weak reference
|
||||||
private ImportActivity importActivity;
|
private ImportActivity importActivity;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
@@ -101,10 +102,10 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
|
||||||
// Get the wake lock if not recording or paused
|
// Get the wake lock if not recording or paused
|
||||||
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
||||||
boolean isPaused = PreferencesUtils.getBoolean(importActivity, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
||||||
if (!isRecording || isPaused) {
|
if (!isRecording || isPaused) {
|
||||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
||||||
}
|
}
|
||||||
@@ -150,9 +151,11 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
|
context = null;
|
||||||
completed = true;
|
completed = true;
|
||||||
if (importActivity != null) {
|
if (importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||||
|
importActivity = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,17 +167,17 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
private boolean importFile(final File file) {
|
private boolean importFile(final File file) {
|
||||||
TrackImporter trackImporter;
|
TrackImporter trackImporter;
|
||||||
if (trackFileFormat == TrackFileFormat.GPX) {
|
if (trackFileFormat == TrackFileFormat.GPX) {
|
||||||
trackImporter = new GpxFileTrackImporter(context);
|
trackImporter = new GpxFileTrackImporter(importActivity);
|
||||||
} else { //KML or KMZ
|
} else { //KML or KMZ
|
||||||
String extension = FileUtils.getExtension(file.getName());
|
String extension = FileUtils.getExtension(file.getName());
|
||||||
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
||||||
trackImporter = new KmlFileTrackImporter(context, -1L);
|
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
|
||||||
} else {
|
} else {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(importActivity);
|
||||||
Uri uri = contentProviderUtils.insertTrack(new Track());
|
Uri uri = contentProviderUtils.insertTrack(new Track());
|
||||||
long newId = Long.parseLong(uri.getLastPathSegment());
|
long newId = Long.parseLong(uri.getLastPathSegment());
|
||||||
|
|
||||||
trackImporter = new KmzTrackImporter(context, newId);
|
trackImporter = new KmzTrackImporter(importActivity, newId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
* @param importTrackId track id to import to. -1L to import to a new track.
|
* @param importTrackId track id to import to. -1L to import to a new track.
|
||||||
*/
|
*/
|
||||||
public KmlFileTrackImporter(Context context, long importTrackId) {
|
public KmlFileTrackImporter(Context context, long importTrackId) {
|
||||||
this(context, importTrackId, ContentProviderUtils.Factory.get(context));
|
this(context, importTrackId, new ContentProviderUtils(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
String fileName = zipEntry.getName();
|
String fileName = zipEntry.getName();
|
||||||
if (fileName.equals(KmzTrackExporter.KMZ_KML_FILE)) {
|
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
|
||||||
trackId = parseKml(zipInputStream);
|
trackId = parseKml(zipInputStream);
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
Log.d(TAG, "Unable to parse kml in kmz");
|
Log.d(TAG, "Unable to parse kml in kmz");
|
||||||
@@ -101,8 +101,8 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @param trackId the trackId
|
* @param trackId the trackId
|
||||||
*/
|
*/
|
||||||
private void cleanImport(long trackId) {
|
private void cleanImport(long trackId) {
|
||||||
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
if (PreferencesUtils.isRecording(trackId)) {
|
||||||
ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
contentProviderUtils.deleteTrack(context, trackId);
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,15 +125,10 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @return the imported track id or -1L
|
* @return the imported track id or -1L
|
||||||
*/
|
*/
|
||||||
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
private long parseKml(ZipInputStream zipInputStream) throws IOException {
|
||||||
ByteArrayInputStream byteArrayInputStream = null;
|
|
||||||
try {
|
|
||||||
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
|
||||||
byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream));
|
|
||||||
|
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
|
||||||
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
return kmlFileTrackImporter.importFile(byteArrayInputStream);
|
||||||
} finally {
|
|
||||||
if (byteArrayInputStream != null) {
|
|
||||||
byteArrayInputStream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,12 +155,10 @@ public class KmzTrackImporter implements TrackImporter {
|
|||||||
* @param fileName the file name
|
* @param fileName the file name
|
||||||
*/
|
*/
|
||||||
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
|
private void readImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
|
||||||
if (importTrackId == -1L) {
|
if (importTrackId == -1L || fileName.equals("")) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fileName.equals("")) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File dir = FileUtils.getPhotoDir(importTrackId);
|
File dir = FileUtils.getPhotoDir(importTrackId);
|
||||||
FileUtils.ensureDirectoryExists(dir);
|
FileUtils.ensureDirectoryExists(dir);
|
||||||
File file = new File(dir, fileName);
|
File file = new File(dir, fileName);
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ public class LocationManagerConnector {
|
|||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final LocationManager locationManager;
|
private final LocationManager locationManager;
|
||||||
|
|
||||||
private LocationListener requestLocationUpdates;
|
|
||||||
private float requestLocationUpdatesDistance;
|
|
||||||
private long requestLocationUpdatesTime;
|
|
||||||
|
|
||||||
public LocationManagerConnector(Context context, Looper looper) {
|
public LocationManagerConnector(Context context, Looper looper) {
|
||||||
this.handler = new Handler(looper);
|
this.handler = new Handler(looper);
|
||||||
|
|
||||||
@@ -55,10 +51,7 @@ public class LocationManagerConnector {
|
|||||||
@Override
|
@Override
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
public void run() {
|
public void run() {
|
||||||
requestLocationUpdatesTime = minTime;
|
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minDistance, locationListener);
|
||||||
requestLocationUpdatesDistance = minDistance;
|
|
||||||
requestLocationUpdates = locationListener;
|
|
||||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, requestLocationUpdatesTime, requestLocationUpdatesDistance, locationListener);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -70,6 +63,5 @@ public class LocationManagerConnector {
|
|||||||
*/
|
*/
|
||||||
public void removeLocationUpdates(final LocationListener locationListener) {
|
public void removeLocationUpdates(final LocationListener locationListener) {
|
||||||
locationManager.removeUpdates(locationListener);
|
locationManager.removeUpdates(locationListener);
|
||||||
requestLocationUpdates = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.services;
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -30,7 +27,6 @@ import android.location.Location;
|
|||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -38,7 +34,6 @@ import android.os.PowerManager.WakeLock;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -48,20 +43,17 @@ import de.dennisguse.opentracks.R;
|
|||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||||
import de.dennisguse.opentracks.TrackListActivity;
|
import de.dennisguse.opentracks.TrackListActivity;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator;
|
|
||||||
import de.dennisguse.opentracks.content.CustomContentProvider;
|
import de.dennisguse.opentracks.content.CustomContentProvider;
|
||||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
import de.dennisguse.opentracks.content.LocationFactory;
|
||||||
|
import de.dennisguse.opentracks.content.LocationIterator;
|
||||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.services.sensors.RemoteSensorManagerFactory;
|
|
||||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||||
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
|
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
@@ -80,36 +72,30 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
*/
|
*/
|
||||||
public class TrackRecordingService extends Service {
|
public class TrackRecordingService extends Service {
|
||||||
|
|
||||||
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
|
private static final int NOTIFICATION_ID = 123;
|
||||||
public static final String RESUME_TRACK_EXTRA_NAME = "de.dennisguse.opentracks.RESUME_TRACK";
|
|
||||||
|
|
||||||
public static final double PAUSE_LATITUDE = 100.0;
|
public static final double PAUSE_LATITUDE = 100.0;
|
||||||
public static final double RESUME_LATITUDE = 200.0;
|
public static final double RESUME_LATITUDE = 200.0;
|
||||||
|
|
||||||
// Anything faster than that (in meters per second) will be considered moving.
|
// Anything faster than that (in meters per second) will be considered moving.
|
||||||
|
|
||||||
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||||
@VisibleForTesting
|
|
||||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
|
||||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||||
// 1 minute in milliseconds
|
// 1 minute in milliseconds
|
||||||
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||||
// The following variables are set in onCreate:
|
// The following variables are set in onCreate:
|
||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
private Context context;
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private LocationManagerConnector locationManagerConnector;
|
private LocationManagerConnector locationManagerConnector;
|
||||||
private PeriodicTaskExecutor voiceExecutor;
|
private PeriodicTaskExecutor voiceExecutor;
|
||||||
private PeriodicTaskExecutor splitExecutor;
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
private TrackRecordingServiceNotificationManager notificationManager;
|
||||||
private long recordingTrackId;
|
private long recordingTrackId;
|
||||||
private boolean recordingTrackPaused;
|
private boolean recordingTrackPaused;
|
||||||
private LocationListenerPolicy locationListenerPolicy;
|
private LocationListenerPolicy locationListenerPolicy;
|
||||||
private int recordingDistanceInterval;
|
private int recordingDistanceInterval;
|
||||||
private int maxRecordingDistance;
|
private int maxRecordingDistance;
|
||||||
private int recordingGpsAccuracy;
|
private int recordingGpsAccuracy;
|
||||||
private int autoResumeTrackTimeout;
|
|
||||||
private long currentRecordingInterval;
|
private long currentRecordingInterval;
|
||||||
|
|
||||||
// The following variables are set when recording:
|
// The following variables are set when recording:
|
||||||
@@ -118,56 +104,47 @@ public class TrackRecordingService extends Service {
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_id_key))) {
|
Context context = TrackRecordingService.this;
|
||||||
|
if (PreferencesUtils.isKey(TrackRecordingService.this, R.string.recording_track_id_key, key)) {
|
||||||
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
|
// Only through the TrackRecordingService can one stop a recording and set the recordingTrackId to -1L.
|
||||||
if (PreferencesUtils.isRecording(context)) {
|
if (PreferencesUtils.isRecording(TrackRecordingService.this)) {
|
||||||
recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackRecordingService.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_track_paused_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.stats_units_key))) {
|
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
voiceExecutor.setMetricUnits(metricUnits);
|
voiceExecutor.setMetricUnits(metricUnits);
|
||||||
splitExecutor.setMetricUnits(metricUnits);
|
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.voice_frequency_key))) {
|
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.voice_frequency_key, PreferencesUtils.VOICE_FREQUENCY_DEFAULT));
|
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.split_frequency_key))) {
|
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||||
splitExecutor.setTaskFrequency(PreferencesUtils.getInt(context, R.string.split_frequency_key, PreferencesUtils.SPLIT_FREQUENCY_DEFAULT));
|
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||||
}
|
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.min_recording_interval_key))) {
|
|
||||||
int minRecordingInterval = PreferencesUtils.getInt(context, R.string.min_recording_interval_key, PreferencesUtils.MIN_RECORDING_INTERVAL_DEFAULT);
|
|
||||||
switch (minRecordingInterval) {
|
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_BATTERY_LIFE:
|
|
||||||
// Choose battery life over moving time accuracy.
|
// Choose battery life over moving time accuracy.
|
||||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
|
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND, 5 * ONE_MINUTE, 5);
|
||||||
break;
|
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||||
case PreferencesUtils.MIN_RECORDING_INTERVAL_ADAPT_ACCURACY:
|
|
||||||
// Get all the updates.
|
// Get all the updates.
|
||||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
|
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND, 30 * UnitConversions.ONE_SECOND, 0);
|
||||||
break;
|
} else {
|
||||||
default:
|
|
||||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
|
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_distance_interval_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||||
recordingDistanceInterval = PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.max_recording_distance_key))) {
|
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||||
maxRecordingDistance = PreferencesUtils.getInt(context, R.string.max_recording_distance_key, PreferencesUtils.MAX_RECORDING_DISTANCE_DEFAULT);
|
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.recording_gps_accuracy_key))) {
|
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||||
recordingGpsAccuracy = PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT);
|
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||||
}
|
|
||||||
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.auto_resume_track_timeout_key))) {
|
|
||||||
autoResumeTrackTimeout = PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private TripStatisticsUpdater markerTripStatisticsUpdater;
|
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||||
private Location lastLocation;
|
private Location lastLocation;
|
||||||
@@ -222,14 +199,13 @@ public class TrackRecordingService extends Service {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
executorService = Executors.newSingleThreadExecutor();
|
executorService = Executors.newSingleThreadExecutor();
|
||||||
context = this;
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
contentProviderUtils = ContentProviderUtils.Factory.get(this);
|
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
||||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||||
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
|
|
||||||
// onSharedPreferenceChanged might not set recordingTrackId.
|
// onSharedPreferenceChanged might not set recordingTrackId.
|
||||||
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||||
@@ -254,7 +230,6 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
handleStartCommand(intent, startId);
|
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,12 +241,12 @@ public class TrackRecordingService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
if (remoteSensorManager != null) {
|
if (remoteSensorManager != null) {
|
||||||
RemoteSensorManagerFactory.releaseSystemSensorManager();
|
remoteSensorManager.stop();
|
||||||
remoteSensorManager = null;
|
remoteSensorManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse order from onCreate
|
// Reverse order from onCreate
|
||||||
showNotification(false);
|
showNotification(false); //TODO Why?
|
||||||
|
|
||||||
handler.removeCallbacks(registerLocationRunnable);
|
handler.removeCallbacks(registerLocationRunnable);
|
||||||
unregisterLocationListener();
|
unregisterLocationListener();
|
||||||
@@ -279,12 +254,6 @@ public class TrackRecordingService extends Service {
|
|||||||
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
|
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
|
||||||
try {
|
|
||||||
splitExecutor.shutdown();
|
|
||||||
} finally {
|
|
||||||
splitExecutor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
voiceExecutor.shutdown();
|
voiceExecutor.shutdown();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -305,23 +274,14 @@ public class TrackRecordingService extends Service {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the service is recording.
|
|
||||||
*/
|
|
||||||
public boolean isRecording() {
|
public boolean isRecording() {
|
||||||
return PreferencesUtils.isRecording(recordingTrackId);
|
return PreferencesUtils.isRecording(recordingTrackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the current recording is paused.
|
|
||||||
*/
|
|
||||||
public boolean isPaused() {
|
public boolean isPaused() {
|
||||||
return recordingTrackPaused;
|
return recordingTrackPaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the trip statistics.
|
|
||||||
*/
|
|
||||||
public TripStatistics getTripStatistics() {
|
public TripStatistics getTripStatistics() {
|
||||||
if (trackTripStatisticsUpdater == null) {
|
if (trackTripStatisticsUpdater == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -340,43 +300,19 @@ public class TrackRecordingService extends Service {
|
|||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaypointType waypointType = waypointCreationRequest.getType();
|
String name = waypointCreationRequest.getName();
|
||||||
boolean isStatistics = waypointType == WaypointType.STATISTICS;
|
if (name == null) {
|
||||||
|
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointCreationRequest.getType());
|
||||||
// Get name
|
|
||||||
String name;
|
|
||||||
if (waypointCreationRequest.getName() != null) {
|
|
||||||
name = waypointCreationRequest.getName();
|
|
||||||
} else {
|
|
||||||
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointType);
|
|
||||||
if (nextWaypointNumber == -1) {
|
if (nextWaypointNumber == -1) {
|
||||||
nextWaypointNumber = 0;
|
nextWaypointNumber = 0;
|
||||||
}
|
}
|
||||||
name = getString(isStatistics ? R.string.marker_split_name_format : R.string.marker_name_format, nextWaypointNumber);
|
name = getString(R.string.marker_name_format, nextWaypointNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get category
|
|
||||||
String category = waypointCreationRequest.getCategory() != null ? waypointCreationRequest.getCategory() : "";
|
String category = waypointCreationRequest.getCategory() != null ? waypointCreationRequest.getCategory() : "";
|
||||||
|
String description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
|
||||||
|
String icon = getString(R.string.marker_waypoint_icon_url);
|
||||||
|
|
||||||
// Get tripStatistics, description, and icon
|
|
||||||
TripStatistics tripStatistics;
|
|
||||||
String description;
|
|
||||||
String icon;
|
|
||||||
if (isStatistics) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
markerTripStatisticsUpdater.updateTime(now);
|
|
||||||
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
|
|
||||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
|
||||||
description = new DescriptionGeneratorImpl(this).generateWaypointDescription(tripStatistics);
|
|
||||||
icon = getString(R.string.marker_statistics_icon_url);
|
|
||||||
} else {
|
|
||||||
tripStatistics = null;
|
|
||||||
description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
|
|
||||||
//TODO Bundle icon?
|
|
||||||
icon = getString(R.string.marker_waypoint_icon_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get length and duration
|
|
||||||
double length;
|
double length;
|
||||||
long duration;
|
long duration;
|
||||||
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
|
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
|
||||||
@@ -399,95 +335,11 @@ public class TrackRecordingService extends Service {
|
|||||||
String photoUrl = waypointCreationRequest.getPhotoUrl() != null ? waypointCreationRequest.getPhotoUrl() : "";
|
String photoUrl = waypointCreationRequest.getPhotoUrl() != null ? waypointCreationRequest.getPhotoUrl() : "";
|
||||||
|
|
||||||
// Insert waypoint
|
// Insert waypoint
|
||||||
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointType, length, duration, -1L, -1L, location, tripStatistics, photoUrl);
|
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointCreationRequest.getType(), length, duration, -1L, -1L, location, null, photoUrl);
|
||||||
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
|
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
|
||||||
return Long.parseLong(uri.getLastPathSegment());
|
return Long.parseLong(uri.getLastPathSegment());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the service as a foreground service.
|
|
||||||
*
|
|
||||||
* @param pendingIntent the notification pending intent
|
|
||||||
* @param messageId the notification message id
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
protected void startForegroundService(PendingIntent pendingIntent, int messageId) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel notificationChannel = new NotificationChannel(getString(R.string.app_name), getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
|
|
||||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (manager != null) {
|
|
||||||
manager.createNotificationChannel(notificationChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.app_name))
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setContentText(getString(messageId))
|
|
||||||
.setContentTitle(getString(R.string.app_name))
|
|
||||||
.setOngoing(true)
|
|
||||||
.setSmallIcon(R.drawable.ic_logo_color_24dp)
|
|
||||||
.setCategory(Notification.CATEGORY_SERVICE)
|
|
||||||
.setWhen(System.currentTimeMillis());
|
|
||||||
startForeground(1, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the service as a foreground service.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
protected void stopForegroundService() {
|
|
||||||
stopForeground(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles start command.
|
|
||||||
*
|
|
||||||
* @param intent the intent
|
|
||||||
* @param startId the start id
|
|
||||||
*/
|
|
||||||
private void handleStartCommand(Intent intent, int startId) {
|
|
||||||
// Check if the service is called to resume track (from phone reboot)
|
|
||||||
boolean resumeTrackRequested = intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false);
|
|
||||||
if (resumeTrackRequested && !shouldResumeTrack()) {
|
|
||||||
Log.i(TAG, "Stop resume track.");
|
|
||||||
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
|
|
||||||
stopSelfResult(startId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if should resume.
|
|
||||||
*/
|
|
||||||
private boolean shouldResumeTrack() {
|
|
||||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
|
||||||
|
|
||||||
if (track == null) {
|
|
||||||
Log.d(TAG, "Not resuming. Track is null.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int retries = PreferencesUtils.getInt(this, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
|
||||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
|
||||||
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, retries + 1);
|
|
||||||
|
|
||||||
if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_NEVER) {
|
|
||||||
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
|
|
||||||
return false;
|
|
||||||
} else if (autoResumeTrackTimeout == PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_ALWAYS) {
|
|
||||||
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track.getTripStatistics() == null) {
|
|
||||||
Log.d(TAG, "Not resuming. No trip statistics.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long stopTime = track.getTripStatistics().getStopTime();
|
|
||||||
return stopTime > 0 && (System.currentTimeMillis() - stopTime) <= autoResumeTrackTimeout * ONE_MINUTE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new track.
|
* Starts a new track.
|
||||||
*
|
*
|
||||||
@@ -500,7 +352,6 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
trackTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
|
||||||
|
|
||||||
// Insert a track
|
// Insert a track
|
||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
@@ -509,17 +360,18 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
// Update shared preferences
|
// Update shared preferences
|
||||||
updateRecordingState(trackId, false);
|
updateRecordingState(trackId, false);
|
||||||
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0);
|
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
track.setId(trackId);
|
track.setId(trackId);
|
||||||
track.setName(TrackNameUtils.getTrackName(this, trackId, now));
|
track.setName(TrackNameUtils.getTrackName(this, trackId, now));
|
||||||
|
|
||||||
String category = PreferencesUtils.getString(this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT);
|
String category = PreferencesUtils.getDefaultActivity(this);
|
||||||
track.setCategory(category);
|
track.setCategory(category);
|
||||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||||
contentProviderUtils.updateTrack(track);
|
contentProviderUtils.updateTrack(track);
|
||||||
|
|
||||||
|
//TODO Do not use insertWaypoint
|
||||||
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
|
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
|
||||||
|
|
||||||
startRecording(true);
|
startRecording(true);
|
||||||
@@ -537,23 +389,11 @@ public class TrackRecordingService extends Service {
|
|||||||
TripStatistics tripStatistics = track.getTripStatistics();
|
TripStatistics tripStatistics = track.getTripStatistics();
|
||||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
|
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
|
||||||
|
|
||||||
long markerStartTime;
|
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||||
Waypoint waypoint = contentProviderUtils.getLastWaypoint(recordingTrackId, WaypointType.STATISTICS);
|
|
||||||
if (waypoint != null && waypoint.getTripStatistics() != null) {
|
|
||||||
markerStartTime = waypoint.getTripStatistics().getStopTime();
|
|
||||||
} else {
|
|
||||||
markerStartTime = tripStatistics.getStartTime();
|
|
||||||
}
|
|
||||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
|
|
||||||
|
|
||||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) {
|
|
||||||
|
|
||||||
while (locationIterator.hasNext()) {
|
while (locationIterator.hasNext()) {
|
||||||
Location location = locationIterator.next();
|
Location location = locationIterator.next();
|
||||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||||
if (location.getTime() > markerStartTime) {
|
|
||||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.e(TAG, "RuntimeException", e);
|
Log.e(TAG, "RuntimeException", e);
|
||||||
@@ -588,14 +428,14 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common code for starting a new track, resuming a track, or restarting after
|
* Common code for starting a new track, resuming a track, or restarting after phone reboot.
|
||||||
* phone reboot.
|
|
||||||
*
|
*
|
||||||
* @param trackStarted true if track is started, false if track is resumed
|
* @param trackStarted true if track is started, false if track is resumed
|
||||||
*/
|
*/
|
||||||
private void startRecording(boolean trackStarted) {
|
private void startRecording(boolean trackStarted) {
|
||||||
// Update instance variables
|
// Update instance variables
|
||||||
remoteSensorManager = RemoteSensorManagerFactory.getSystemSensorManager(this);
|
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||||
|
remoteSensorManager.start();
|
||||||
lastLocation = null;
|
lastLocation = null;
|
||||||
currentSegmentHasLocation = false;
|
currentSegmentHasLocation = false;
|
||||||
isIdle = false;
|
isIdle = false;
|
||||||
@@ -604,7 +444,6 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
// Restore periodic tasks
|
// Restore periodic tasks
|
||||||
voiceExecutor.restore();
|
voiceExecutor.restore();
|
||||||
splitExecutor.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -649,7 +488,7 @@ public class TrackRecordingService extends Service {
|
|||||||
contentProviderUtils.updateTrack(track);
|
contentProviderUtils.updateTrack(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endRecording(true, trackId);
|
endRecording(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -677,23 +516,23 @@ public class TrackRecordingService extends Service {
|
|||||||
insertLocation(track, pause, null);
|
insertLocation(track, pause, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
endRecording(false, recordingTrackId);
|
endRecording(false);
|
||||||
|
|
||||||
|
notificationManager.updateContent(getString(R.string.generic_paused));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common code for ending a track or pausing a track.
|
* Common code for ending a track or pausing a track.
|
||||||
*
|
*
|
||||||
* @param trackStopped true if track is stopped, false if track is paused
|
* @param trackStopped true if track is stopped, false if track is paused
|
||||||
* @param trackId the track id
|
|
||||||
*/
|
*/
|
||||||
private void endRecording(boolean trackStopped, long trackId) {
|
private void endRecording(boolean trackStopped) {
|
||||||
// Shutdown periodic tasks
|
// Shutdown periodic tasks
|
||||||
voiceExecutor.shutdown();
|
voiceExecutor.shutdown();
|
||||||
splitExecutor.shutdown();
|
|
||||||
|
|
||||||
// Update instance variables
|
// Update instance variables
|
||||||
if (remoteSensorManager != null) {
|
if (remoteSensorManager != null) {
|
||||||
RemoteSensorManagerFactory.releaseSystemSensorManager();
|
remoteSensorManager.stop();
|
||||||
remoteSensorManager = null;
|
remoteSensorManager = null;
|
||||||
}
|
}
|
||||||
lastLocation = null;
|
lastLocation = null;
|
||||||
@@ -764,6 +603,8 @@ public class TrackRecordingService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationManager.updateLocation(this, location, recordingGpsAccuracy);
|
||||||
|
|
||||||
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
|
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
|
||||||
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
||||||
return;
|
return;
|
||||||
@@ -799,10 +640,7 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
|
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
|
||||||
/*
|
// Should not happen. The current segment should have a location. Just insert the current location.
|
||||||
* Should not happen. The current segment should have a location. Just
|
|
||||||
* insert the current location.
|
|
||||||
*/
|
|
||||||
insertLocation(track, location, null);
|
insertLocation(track, location, null);
|
||||||
lastLocation = location;
|
lastLocation = location;
|
||||||
return;
|
return;
|
||||||
@@ -867,7 +705,6 @@ public class TrackRecordingService extends Service {
|
|||||||
Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId());
|
Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId());
|
||||||
long trackPointId = Long.parseLong(uri.getLastPathSegment());
|
long trackPointId = Long.parseLong(uri.getLastPathSegment());
|
||||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
|
||||||
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
|
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
/*
|
/*
|
||||||
@@ -877,7 +714,6 @@ public class TrackRecordingService extends Service {
|
|||||||
Log.w(TAG, "SQLiteException", e);
|
Log.w(TAG, "SQLiteException", e);
|
||||||
}
|
}
|
||||||
voiceExecutor.update();
|
voiceExecutor.update();
|
||||||
splitExecutor.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -949,33 +785,33 @@ public class TrackRecordingService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the notification.
|
|
||||||
*
|
|
||||||
* @param isGpsStarted true if GPS is started
|
|
||||||
*/
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if ((isRecording() && isPaused()) || (!isRecording() && !isGpsStarted)) {
|
||||||
if (isPaused()) {
|
stopForeground(true);
|
||||||
stopForegroundService();
|
}
|
||||||
} else {
|
|
||||||
|
if (isRecording() && !isPaused()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.addParentStack(TrackDetailActivity.class).addNextIntent(intent)
|
.addParentStack(TrackDetailActivity.class)
|
||||||
|
.addNextIntent(intent)
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
startForegroundService(pendingIntent, R.string.track_record_notification);
|
|
||||||
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
|
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
|
||||||
}
|
}
|
||||||
} else {
|
if (!isRecording() && isGpsStarted) {
|
||||||
// Not recording
|
|
||||||
if (isGpsStarted) {
|
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.addNextIntent(intent).getPendingIntent(0, 0);
|
.addNextIntent(intent)
|
||||||
startForegroundService(pendingIntent, R.string.gps_starting);
|
.getPendingIntent(0, 0);
|
||||||
} else {
|
|
||||||
stopForegroundService();
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
}
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
|
|
||||||
|
startForeground(NOTIFICATION_ID, notificationManager.getNotification());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,15 +904,7 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SensorDataSet getSensorData() {
|
public SensorDataSet getSensorData() {
|
||||||
if (trackRecordingService.remoteSensorManager == null) {
|
return trackRecordingService.getSensorDataSet();
|
||||||
Log.d(TAG, "remoteSensorManager is null.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (trackRecordingService.getSensorDataSet() == null) {
|
|
||||||
Log.d(TAG, "Sensor data set is null.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return trackRecordingService.remoteSensorManager.getSensorDataSet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
|
|
||||||
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
|
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
//TODO Always the context of the owner of a service connection (i.e., let it be passed to every call).
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final Runnable callback;
|
private final Runnable callback;
|
||||||
|
|
||||||
@@ -203,9 +205,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
if (PreferencesUtils.isRecording(context)) {
|
if (PreferencesUtils.isRecording(context)) {
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
PreferencesUtils.setLong(context, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||||
}
|
}
|
||||||
boolean recordingTrackPaused = PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
boolean recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||||
if (!recordingTrackPaused) {
|
if (!recordingTrackPaused) {
|
||||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT);
|
PreferencesUtils.defaultRecordingTrackPaused(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +250,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
try {
|
try {
|
||||||
if (showEditor) {
|
if (showEditor) {
|
||||||
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
|
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
|
||||||
long recordingTrackId = PreferencesUtils.getLong(context, R.string.recording_track_id_key);
|
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||||
trackRecordingService.endCurrentTrack();
|
trackRecordingService.endCurrentTrack();
|
||||||
if (PreferencesUtils.isRecording(context)) {
|
if (PreferencesUtils.isRecording(context)) {
|
||||||
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
|
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the content of the notification shown by {@link TrackRecordingService}.
|
||||||
|
*/
|
||||||
|
class TrackRecordingServiceNotificationManager {
|
||||||
|
|
||||||
|
private final static int NOTIFICATION_ID = 123;
|
||||||
|
|
||||||
|
private final static String CHANNEL_ID = TrackRecordingServiceNotificationManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private NotificationCompat.Builder notificationBuilder;
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
private boolean previousLocationWasAccurate = true;
|
||||||
|
|
||||||
|
TrackRecordingServiceNotificationManager(Context context) {
|
||||||
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
notificationChannel.setAllowBubbles(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(notificationChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||||
|
notificationBuilder
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
.setContentTitle(context.getString(R.string.app_name))
|
||||||
|
.setSmallIcon(R.drawable.ic_logo_color_24dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
TrackRecordingServiceNotificationManager(NotificationManager notificationManager, NotificationCompat.Builder notificationBuilder) {
|
||||||
|
this.notificationManager = notificationManager;
|
||||||
|
this.notificationBuilder = notificationBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateContent(String content) {
|
||||||
|
notificationBuilder.setContentText(content);
|
||||||
|
updateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLocation(Context context, Location location, int recordingGpsAccuracy) {
|
||||||
|
String formattedAccuracy = context.getString(R.string.value_none);
|
||||||
|
if (location.hasAccuracy()) {
|
||||||
|
formattedAccuracy = StringUtils.formatDistance(context, location.getAccuracy(), PreferencesUtils.isMetricUnits(context));
|
||||||
|
|
||||||
|
boolean currentLocationWasAccurate = location.getAccuracy() < recordingGpsAccuracy;
|
||||||
|
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
|
||||||
|
notificationBuilder.setOnlyAlertOnce(!shouldAlert);
|
||||||
|
previousLocationWasAccurate = currentLocationWasAccurate;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder.setContentText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
|
||||||
|
updateNotification();
|
||||||
|
notificationBuilder.setOnlyAlertOnce(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePendingIntent(PendingIntent pendingIntent) {
|
||||||
|
notificationBuilder.setContentIntent(pendingIntent);
|
||||||
|
updateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification getNotification() {
|
||||||
|
return notificationBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNotification() {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, getNotification());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothGatt;
|
|||||||
import android.bluetooth.BluetoothGattCallback;
|
import android.bluetooth.BluetoothGattCallback;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.bluetooth.BluetoothGattDescriptor;
|
import android.bluetooth.BluetoothGattDescriptor;
|
||||||
|
import android.bluetooth.BluetoothGattService;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -36,7 +37,6 @@ import de.dennisguse.opentracks.util.BluetoothUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages connection to Bluetooth LE heart rate monitor.
|
* Manages connection to Bluetooth LE heart rate monitor.
|
||||||
* TODO: shutdown connection.
|
|
||||||
*/
|
*/
|
||||||
public class BluetoothConnectionManager {
|
public class BluetoothConnectionManager {
|
||||||
|
|
||||||
@@ -46,7 +46,6 @@ public class BluetoothConnectionManager {
|
|||||||
static final int MESSAGE_READ = 3;
|
static final int MESSAGE_READ = 3;
|
||||||
static final int MESSAGE_DISCONNECTED = 4;
|
static final int MESSAGE_DISCONNECTED = 4;
|
||||||
|
|
||||||
private static final UUID HEART_RATE_SERVICE_UUID = new UUID(0x180D00001000L, 0x800000805f9b34fbL);
|
|
||||||
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
private static final UUID HEART_RATE_MEASUREMENT_CHAR_UUID = new UUID(0x2A3700001000L, 0x800000805f9b34fbL);
|
||||||
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
||||||
|
|
||||||
@@ -91,10 +90,18 @@ public class BluetoothConnectionManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
||||||
BluetoothGattCharacteristic characteristic = gatt
|
BluetoothGattService service = gatt.getService(BluetoothUtils.HEART_RATE_SERVICE_UUID);
|
||||||
.getService(HEART_RATE_SERVICE_UUID)
|
if (service == null) {
|
||||||
.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
|
Log.e(TAG, "Could not get heart rate service for " + gatt.getDevice().getAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic characteristic = service.getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);
|
||||||
|
if (characteristic == null) {
|
||||||
|
Log.e(TAG, "Could not get BluetoothCharacteristic for " + gatt.getDevice().getAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
gatt.setCharacteristicNotification(characteristic, true);
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
|
|
||||||
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
|
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
|
||||||
@@ -132,8 +139,9 @@ public class BluetoothConnectionManager {
|
|||||||
|
|
||||||
Log.d(TAG, "Connecting to: " + bluetoothDevice);
|
Log.d(TAG, "Connecting to: " + bluetoothDevice);
|
||||||
|
|
||||||
setState(SensorState.CONNECTING);
|
|
||||||
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
|
bluetoothGatt = bluetoothDevice.connectGatt(this.context, true, this.connectCallback);
|
||||||
|
|
||||||
|
setState(SensorState.CONNECTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void disconnect() {
|
public synchronized void disconnect() {
|
||||||
|
|||||||