forked from upstream-mirrors/OpenTracks
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1feb5cbb65 | |||
| 1d2fde6e69 | |||
| 20423121ee | |||
| ebf7b0a0c3 | |||
| 1189a07794 | |||
| 63e9041922 | |||
| 699be450b0 | |||
| 66b175c56c | |||
| 81a7aca4a0 | |||
| 065f42fbef | |||
| c92371b36a | |||
| 97c46183c3 | |||
| c02b8fab53 | |||
| 6540913d59 | |||
| b70c3be5af | |||
| 461f1c246b | |||
| 6d091dfa81 | |||
| 97ad6bc2f8 | |||
| 93ee8a53ed | |||
| bf9b6d6627 | |||
| 5f637952aa | |||
| 17de8cea6f | |||
| afea79476f | |||
| 7215a2def0 | |||
| b8d2d14d68 | |||
| a2b53c75f4 | |||
| 7a5f69d3a5 | |||
| 7fd9893560 | |||
| f209031df0 | |||
| 2dbab929ac | |||
| e9eb764d12 | |||
| 41a4a5527a | |||
| 4ded3c6bc8 | |||
| 51e573e475 | |||
| 98c3169929 | |||
| ff65a4d04f | |||
| 278f49dec8 | |||
| d444e1ca3c | |||
| 13e07696b9 | |||
| 23c3a4d651 | |||
| c9869bab0e | |||
| ecdb509371 | |||
| de376a84f8 | |||
| 9abd8bcaa6 | |||
| 8ddadb8e6d | |||
| 924ef3313e | |||
| 8cb68a6e64 | |||
| 23f47f10f8 | |||
| 251fa17487 | |||
| bae3373fe9 | |||
| 0a685a66e3 | |||
| 5ca9ca857a | |||
| d9dfa6d746 | |||
| 3a5220546e | |||
| 047d5c2340 | |||
| 725c21c0a1 | |||
| 7939e39fc1 | |||
| a133bd8f9e | |||
| 1f9790cba8 | |||
| a3ba1e1490 | |||
| accb30152f | |||
| dc7daa6e8c | |||
| 1d8e788d53 | |||
| 568b908b26 | |||
| 3ab63adb34 | |||
| 91692f345e | |||
| dbdb98a682 | |||
| df66cd5753 | |||
| 6e39dd37f8 | |||
| 7212f7be94 | |||
| 77fe494e6c | |||
| 2f491e8c10 | |||
| f4fe7b1ab3 | |||
| 7e444cbf31 | |||
| ed6ab9c53c | |||
| f91ca3154c | |||
| bbfab1b201 | |||
| 1f2e3bcf70 | |||
| d1f3791f97 | |||
| 490b95a001 | |||
| 12e6f03098 | |||
| d86f42e2e9 | |||
| 360d59d3de | |||
| b617420290 | |||
| 53b267150b | |||
| 0dfd55ed52 | |||
| daabd5381d | |||
| a842129e37 | |||
| e8b9c2bbc3 | |||
| e55924dae3 | |||
| e3a8143d58 | |||
| 30310bb874 | |||
| 9f377b172d |
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -51,13 +51,19 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
__Only required permissions:__
|
||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||
|
||||
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||
|
||||
_Please note:_
|
||||
_OpenTracks_ 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.
|
||||
|
||||
## Custom Dashboards
|
||||
## Custom Dashboards (incl. Map)
|
||||
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
||||
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
||||
The developer documentation is in [README_API.md](README_API.md).
|
||||
|
||||
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (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)).
|
||||
However, this is rather slow and does not provide updates while recording.
|
||||
|
||||
|
||||
## Screenshots
|
||||
<div>
|
||||
@@ -87,7 +93,7 @@ Rework of _OpenTracks_ included:
|
||||
* removing calorie estimation and activity estimation,
|
||||
* removing support for ANT+ and Classic Bluetooth,
|
||||
* adding support for Bluetooth LE heart rate sensors,
|
||||
* removing Protobuf (store sensor data in SQLite columns directly), and
|
||||
* removing Protobuf (store sensosensorr data in SQLite columns directly), and
|
||||
* removing Android Service API for other apps.
|
||||
|
||||
Artwork, logos and user interface remained more or less unchanged.
|
||||
|
||||
@@ -7,14 +7,11 @@ Although this might be (mostly) sufficient, it has some limitations.
|
||||
This include limited configurability (e.g., ordering of shown elements) and the lack of Internet access.
|
||||
In fact, the later is considered a feature but severely limits the implementation presenting a map (at least in an easy way).
|
||||
|
||||
_NOTE:_ for showing a track on a map a workaround was implemented in OpenTracks.
|
||||
Data could be exported as KMZ and presented in 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)).
|
||||
|
||||
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
|
||||
|
||||
## Implementation
|
||||
OpenTracks by default does not expose any data to 3rd-party applications.
|
||||
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary) as well as the `TrackPoint`s of one or more selected tracks.
|
||||
On the request of the user (i.e., pressing a button), an Intent is created that exposes the `Track` data (it's summary), the `TrackPoint`s, and the `Waypoint`s of one or more selected tracks.
|
||||
For each, one URI is provided.
|
||||
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Craft new releases
|
||||
|
||||
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
||||
1. Get next _version code_ (main): `git rev-list HEAD --count main`
|
||||
2. Decide on _version name_ (semantic versioning)
|
||||
3. Manually update _version code_ and _version name_ in `build.gradle`
|
||||
4. Create changelog (`_version code_.txt`)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Overview of tested Bluetooth LE sensors
|
||||
|
||||
## HRS: Heartrate Service
|
||||
* Decathlon|Geonaute|Kalenji Dual HRM Belt
|
||||
* Polar H7
|
||||
* Polar OH1
|
||||
Has updatable firmware that requires a user account at polar.com
|
||||
* Wahoo Tickr (Model: WFBTHR02)
|
||||
|
||||
## CSCP: Cycling Cadence and Speed Service
|
||||
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
|
||||
However, often only one value is provided.
|
||||
|
||||
### Speed
|
||||
* Wahoo Speed (Model: WFRPMSPD)
|
||||
|
||||
### Cadence
|
||||
* Wahoo Cadence (Model: WFPODCAD2)
|
||||
This sensor reports cadence data as speed.
|
||||
A workaround is in place.
|
||||
+16
-30
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,6 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionCode = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'rev-list', 'HEAD', '--count'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return Integer.parseInt(stdout.toString().trim())
|
||||
}
|
||||
catch (ignored) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
def getVersionName = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
@@ -50,25 +36,29 @@ def getVersionName = { ->
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
||||
applicationLogo: "@mipmap/ic_launcher",
|
||||
applicationName: "@string/app_name"
|
||||
]
|
||||
|
||||
versionCode 3381
|
||||
//versionCode getVersionCode() //Useful for debugging.
|
||||
versionName "v3.7.2"
|
||||
versionCode 3474
|
||||
versionName "v3.8.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -78,7 +68,7 @@ android {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-debug"
|
||||
manifestPlaceholders = [
|
||||
applicationLogo: "@drawable/ic_logo_black_24dp",
|
||||
applicationLogo: "@mipmap/ic_launcher_debug",
|
||||
applicationName: "OpenTracks (Debug)"
|
||||
]
|
||||
}
|
||||
@@ -100,18 +90,14 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.0.0'
|
||||
|
||||
testImplementation 'androidx.test:core:1.2.0'
|
||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.4.4'
|
||||
}
|
||||
|
||||
+28
-13
@@ -9,23 +9,35 @@ from every activity showing a menu.
|
||||
endlegend
|
||||
|
||||
[*] --> TrackListActivity
|
||||
TrackListActivity --> TrackDetailActivity : Start recording
|
||||
TrackListActivity --> TrackRecordingActivity : Start recording
|
||||
TrackListActivity --> SearchListActivity
|
||||
TrackListActivity --> MarkerListActivity : Show all markers
|
||||
TrackListActivity --> TrackDetailActivity : Show track
|
||||
TrackListActivity --> AggregatedStatisticsActivity : Aggregated stats
|
||||
TrackListActivity --> TrackRecordedActivity : Show track
|
||||
TrackListActivity --> TrackDeleteActivity
|
||||
|
||||
SearchListActivity --> MarkerDetailActivity : Search result
|
||||
SearchListActivity --> TrackDetailActivity : Search result
|
||||
SearchListActivity --> TrackRecordedActivity : Search result
|
||||
|
||||
TrackDetailActivity --> TrackDeleteActivity
|
||||
TrackDetailActivity --> TrackEditActivity
|
||||
TrackDetailActivity --> MarkerListActivity
|
||||
TrackDetailActivity --> MarkerEditActivity : Add marker
|
||||
TrackDetailActivity --> CameraIntent : Add marker (with photo)
|
||||
TrackDetailActivity --> ShareFileIntent
|
||||
TrackDetailActivity --> DashboardIntent : Database cursor
|
||||
TrackDetailActivity --> TrackOnMapIntent : KMZ
|
||||
TrackRecordedActivity --> TrackDeleteActivity
|
||||
TrackRecordedActivity --> TrackEditActivity
|
||||
TrackRecordedActivity --> MarkerListActivity
|
||||
TrackRecordedActivity --> MarkerEditActivity : Add marker
|
||||
TrackRecordedActivity --> CameraIntent : Add marker (with photo)
|
||||
TrackRecordedActivity --> ShareFileIntent
|
||||
TrackRecordedActivity --> DashboardIntent : Database cursor
|
||||
TrackRecordedActivity --> TrackOnMapIntent : KMZ
|
||||
TrackRecordedActivity --> TrackRecordingActivity
|
||||
TrackRecordedActivity --> SettingsActivity
|
||||
TrackRecordedActivity --> TrackListActivity : Back button
|
||||
|
||||
TrackRecordingActivity --> TrackOnMapIntent
|
||||
TrackRecordingActivity --> MarkerEditActivity : Add marker
|
||||
TrackRecordingActivity --> MarkerListActivity
|
||||
TrackRecordingActivity --> TrackEditActivity
|
||||
TrackRecordingActivity --> SettingsActivity
|
||||
TrackRecordingActivity --> TrackRecordedActivity : Stop recording
|
||||
TrackRecordingActivity --> TrackListActivity : Back button
|
||||
|
||||
MarkerListActivity --> MarkerDetailActivity
|
||||
|
||||
@@ -35,6 +47,9 @@ MarkerDetailActivity --> CoordinateOnMapIntent : URI
|
||||
SettingsActivity --> AboutActivity
|
||||
SettingsActivity --> HelpActivity
|
||||
SettingsActivity --> ExportActivity
|
||||
SettingsActivity --> ImportActivity
|
||||
SettingsActivity --> DirectoryChooserActivity
|
||||
SettingsActivity --> DirectoryChooserActivity
|
||||
DirectoryChooserActivity$ImportDirectoryChooserActivity --> ImportActivity
|
||||
DirectoryChooserActivity$ExportDirectoryChooserActivity --> ExportActivity
|
||||
|
||||
@enduml
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
@startuml
|
||||
SensorManager --> ElevationSumManager
|
||||
LocationManager --> TrackRecordingService
|
||||
ElevationSumManager --> TrackRecordingService
|
||||
|
||||
TrackRecordingService --> TrackRecordingServiceConnection
|
||||
TrackRecordingService --> Database
|
||||
|
||||
Database --> CustomContentProvider
|
||||
CustomContentProvider --> TrackDataHub
|
||||
CustomContentProvider --> ShareContentProvider
|
||||
TrackDataHub --> StatisticsRecordingFragment : TrackPoints, TrackStatistics, Waypoints (push)
|
||||
TrackDataHub --> StatisticsRecordedFragment : TrackPoints, TrackStatistics, Waypoints (push)
|
||||
TrackDataHub --> ChartFragment --> ChartView
|
||||
|
||||
TrackRecordingServiceConnection --> StatisticsRecordingFragment : Sensor data (heartrate, cadence, elevation gain; pull 1s)
|
||||
TrackRecordingServiceConnection --> TrackController : Start time (pull, onStart)
|
||||
@enduml
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="ic_marker_orange_pushpin_with_shadow.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient871"
|
||||
|
||||
x1="91.712517"
|
||||
y1="266.48581"
|
||||
x2="95.902588"
|
||||
y2="266.72339"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(22.027833,208.46275,168.26892)"
|
||||
|
||||
|
||||
>
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop867" />
|
||||
<stop
|
||||
style="stop-color:#898d8d;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop869" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.49497475"
|
||||
inkscape:cx="298.98541"
|
||||
inkscape:cy="561.0427"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1012"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="1080"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
d="m 108.52826,213.29755 a 56.035402,7.0061195 5.281088 0 0 -22.425508,2.5321 56.035402,7.0061195 5.281088 0 0 -0.233576,0.17697 l -0.206706,-0.0237 c -0.04471,0.11554 -0.07454,0.2314 -0.09612,0.34752 a 56.035402,7.0061195 5.281088 0 0 0.337963,1.58159 c 0.980726,1.70884 3.760629,3.46326 6.583579,5.2188 l -22.744347,15.50009 a 67.064653,11.556784 4.2275244 0 0 -39.967588,5.26867 67.064653,11.556784 4.2275244 0 0 -0.729154,0.57564 67.064653,11.556784 4.2275244 0 0 -2.754353,1.36358 67.064653,11.556784 4.2275244 0 0 38.908736,16.04324 l -32.176847,27.0646 52.228334,-24.74551 a 67.064653,11.556784 4.2275244 0 0 71.411147,-3.8186 67.064653,11.556784 4.2275244 0 0 0.72812,-0.57507 67.064653,11.556784 4.2275244 0 0 2.75539,-1.36415 67.064653,11.556784 4.2275244 0 0 -12.38374,-10.59818 40.033569,5.0733922 5.2502179 0 0 0.0563,-0.0369 40.033569,5.0733922 5.2502179 0 0 0.37672,-0.34325 l 22.45186,-15.30075 c 8.23597,-0.65161 17.65581,-1.47811 22.94433,-3.00957 a 56.035402,7.0061195 5.281088 0 0 2.65514,-1.03772 56.035402,7.0061195 5.281088 0 0 -46.08401,-12.26953 56.035402,7.0061195 5.281088 0 0 -41.6357,-2.54991 z"
|
||||
style="fill:#333333;fill-opacity:0.49421525;stroke:#000000;stroke-width:0.138931px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
|
||||
id="path839" />
|
||||
<path
|
||||
style="fill:url(#linearGradient871);fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
|
||||
d="M 70.603317,174.30832 33.024148,288.94657 94.566672,183.19473 Z"
|
||||
id="path865"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<ellipse
|
||||
cx="144.42586"
|
||||
cy="113.06232"
|
||||
rx="70.318817"
|
||||
ry="39.974442"
|
||||
id="ellipse26"
|
||||
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
transform="rotate(22.027833)" />
|
||||
<ellipse
|
||||
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
id="ellipse30"
|
||||
ry="39.974442"
|
||||
rx="70.318817"
|
||||
cy="105.23625"
|
||||
cx="145.017"
|
||||
transform="rotate(22.027833)" />
|
||||
<path
|
||||
id="rect28"
|
||||
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
d="m 85.662408,24.322061 c -0.890996,8.350369 2.928035,17.133472 6.825491,25.923906 l -23.333902,57.672763 -0.730686,1.80598 0.248621,0.10059 a 42.43035,17.360788 22.027833 0 0 33.303948,30.10067 42.43035,17.360788 22.027833 0 0 44.86507,1.52585 l 0.13892,0.0562 0.24519,-0.60599 a 42.43035,17.360788 22.027833 0 0 0.59472,-1.15581 42.43035,17.360788 22.027833 0 0 0.37668,-1.24514 l 22.45185,-55.492655 c 9.28986,-2.66566 20.09069,-6.13552 24.76397,-13.17485 z" />
|
||||
<ellipse
|
||||
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
|
||||
id="path24"
|
||||
ry="23.966745"
|
||||
rx="59.409279"
|
||||
cy="-10.262156"
|
||||
cx="148.14029"
|
||||
transform="rotate(22.027833)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -1 +1 @@
|
||||
OpenTracks – 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,7 @@
|
||||
v3.7.3: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Fixed computation of cadence and speed (via Bluetooth LE sensor)
|
||||
|
||||
Developer:
|
||||
- Removed unused translations
|
||||
@@ -0,0 +1,12 @@
|
||||
v3.7.4: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Sharing/export format configurable
|
||||
|
||||
Bugfix:
|
||||
- Wheel diameter not stored correctly (for speed sensor)
|
||||
- Help: links are clickable
|
||||
|
||||
Developer:
|
||||
- Split TrackDetailActivity
|
||||
- StatsFragement successor uses ConstraintLayout
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.7.5: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Added aggregated statistics summary (from main the screen's menu)
|
||||
- Dashboard API: pass "show on lock screen"- and "keep screen on"-setting
|
||||
- Updated translations
|
||||
- Import: be lenient with ISO8601 date format
|
||||
- Show track on map: file format can be selected and opening dashboard works on Android R (API 30+)
|
||||
|
||||
Bugfix:
|
||||
- App icon now correctly implemented
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.7.6: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Landscape mode
|
||||
- Track deletion UI is now only a spinner
|
||||
- Speed is exported and imported (GPX/KML/KMZ)
|
||||
- Default export format is KMZ incl. all data and pictures
|
||||
|
||||
Developer:
|
||||
- Replaced AsyncTasks with Threads
|
||||
- Synchronization: UUID is generated per track (not yet exported)
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.8.0: OpenTracks
|
||||
|
||||
Breaking change:
|
||||
- GPX import/export speed, cadence, and heartrate via TrackPointExtension/v2
|
||||
|
||||
Changes:
|
||||
- Dashboard API: provide access to markers (incl. callback to show in OpenTracks)
|
||||
- New marker icon (orange push pin, SVG)
|
||||
- Files to import can be selected via file manager
|
||||
|
||||
Bugfixes:
|
||||
- Aggregated stats UI respects preferred rate
|
||||
- Editing a marker failed on orientation change
|
||||
|
||||
Developer:
|
||||
- Refactored TrackRecordingService
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.8.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- While recording: add option for fullscreen
|
||||
- Dashboard is started on the same task
|
||||
|
||||
Bugfixes:
|
||||
- Intent filters for importing files were far too optimistic
|
||||
@@ -1 +1 @@
|
||||
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
|
||||
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
|
||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
||||
#Sun Apr 12 18:41:12 CEST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
||||
@@ -82,6 +82,7 @@ esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -129,6 +130,7 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -175,14 +177,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+104
-100
@@ -1,100 +1,104 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
@@ -58,7 +58,6 @@ public class SearchEngineTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
providerUtils = new ContentProviderUtils(context);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class TestDataUtil {
|
||||
|
||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
trackPoints[i] = (createTrackPoint(i));
|
||||
trackPoints[i] = createTrackPoint(i);
|
||||
}
|
||||
|
||||
return new Pair<>(track, trackPoints);
|
||||
@@ -56,17 +56,22 @@ public class TestDataUtil {
|
||||
/**
|
||||
* Creates a location.
|
||||
*
|
||||
* @param i the index to set the value of location.
|
||||
* @return created location
|
||||
* @param i the index for the TrackPoint.
|
||||
*/
|
||||
public static TrackPoint createTrackPoint(int i) {
|
||||
Location location = new Location("test");
|
||||
location.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
location.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
location.setAccuracy((float) i / 100.0f);
|
||||
location.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
location.setTime(i + 1);
|
||||
return new TrackPoint(location);
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
trackPoint.setAccuracy((float) i / 100.0f);
|
||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
trackPoint.setTime(i + 1);
|
||||
trackPoint.setSpeed(5f + (i / 10));
|
||||
|
||||
trackPoint.setHeartRate_bpm(100f + i);
|
||||
trackPoint.setCyclingCadence_rpm(200f + i);
|
||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
||||
trackPoint.setPower(400f + i);
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-13
@@ -37,6 +37,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -47,6 +48,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -171,22 +173,26 @@ public class CustomContentProviderUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
int startColumnIndex = 1;
|
||||
int columnIndex = startColumnIndex;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex++);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex++);
|
||||
columnIndex = startColumnIndex;
|
||||
// Id
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
// Name
|
||||
when(cursorMock.isNull(columnIndex++)).thenReturn(false);
|
||||
long trackId = System.currentTimeMillis();
|
||||
columnIndex = startColumnIndex;
|
||||
|
||||
int columnIndex = 1;
|
||||
// Id
|
||||
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getLong(columnIndex)).thenReturn(trackId);
|
||||
|
||||
//Uuid
|
||||
columnIndex++;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.UUID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getBlob(columnIndex)).thenReturn(UUIDUtils.toBytes(UUID.randomUUID()));
|
||||
|
||||
// Name
|
||||
columnIndex++;
|
||||
String name = NAME_PREFIX + trackId;
|
||||
when(cursorMock.getString(columnIndex++)).thenReturn(name);
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getString(columnIndex)).thenReturn(name);
|
||||
|
||||
Track track = contentProviderUtils.createTrack(cursorMock);
|
||||
Assert.assertEquals(trackId, track.getId());
|
||||
@@ -200,7 +206,7 @@ public class CustomContentProviderUtilsTest {
|
||||
public void testDeleteAllTracks() {
|
||||
// Insert track, points and waypoint at first.
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||
|
||||
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
+27
-22
@@ -5,6 +5,7 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -36,7 +37,8 @@ public class CustomSQLiteOpenHelperTest {
|
||||
*
|
||||
* @return Map(TableName, SQL)
|
||||
*/
|
||||
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||
@VisibleForTesting
|
||||
public static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> tableSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
|
||||
if (cursor != null) {
|
||||
@@ -53,6 +55,7 @@ public class CustomSQLiteOpenHelperTest {
|
||||
*
|
||||
* @return Map(tableName, SQL)
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> indexSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
||||
@@ -104,24 +107,7 @@ public class CustomSQLiteOpenHelperTest {
|
||||
|
||||
@Test
|
||||
public void onUpgrade_FromVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
{
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}.getWritableDatabase();
|
||||
|
||||
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||
|
||||
dbBase.close();
|
||||
}
|
||||
createVersion23();
|
||||
|
||||
// Open database with SQL upgrade
|
||||
Map<String, String> tableByUpgrade;
|
||||
@@ -130,10 +116,9 @@ public class CustomSQLiteOpenHelperTest {
|
||||
tableByUpgrade = getTableSQL(dbUpgraded);
|
||||
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
||||
}
|
||||
|
||||
// Open database via
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
|
||||
// Open database via creation script
|
||||
Map<String, String> tablesByCreate;
|
||||
Map<String, String> indicesByCreate;
|
||||
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||
@@ -150,7 +135,8 @@ public class CustomSQLiteOpenHelperTest {
|
||||
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
|
||||
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(2, indicesByCreate.size());
|
||||
Assert.assertEquals(3, indicesByCreate.size());
|
||||
Assert.assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
|
||||
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
|
||||
}
|
||||
@@ -176,4 +162,23 @@ public class CustomSQLiteOpenHelperTest {
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(0, indicesByDowngrade.size());
|
||||
}
|
||||
|
||||
private void createVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}.getWritableDatabase();
|
||||
|
||||
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||
|
||||
dbBase.close();
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -11,7 +11,14 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
public class ShareContentProviderTest {
|
||||
|
||||
@Test
|
||||
public void testCreateandParseURI() {
|
||||
public void testCreateandParseURI_invalid() {
|
||||
Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1");
|
||||
|
||||
Assert.assertArrayEquals(new long[]{}, ShareContentProvider.parseURI(uri));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateandParseURI_valid() {
|
||||
long[] trackIds = {1, 3, 5};
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
||||
|
||||
|
||||
+27
-1
@@ -12,7 +12,7 @@ import de.dennisguse.opentracks.util.UintUtils;
|
||||
public class SensorDataCyclingTest {
|
||||
|
||||
@Test
|
||||
public void compute_cadence() {
|
||||
public void compute_cadence_1() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024); // 1s
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 2048); // 2s
|
||||
@@ -24,6 +24,19 @@ public class SensorDataCyclingTest {
|
||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_2() {
|
||||
// given
|
||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 8016);
|
||||
|
||||
// when
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(33.53, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_sameCount() {
|
||||
// given
|
||||
@@ -77,6 +90,19 @@ public class SensorDataCyclingTest {
|
||||
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2150);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(1.20, current.getSpeed_mps(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
|
||||
+71
-12
@@ -17,6 +17,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
@@ -48,7 +49,10 @@ public class ExportImportTest {
|
||||
private static final String TRACK_ICON = "the track icon";
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
|
||||
private long importTrackId;
|
||||
private final long trackId = System.currentTimeMillis();
|
||||
|
||||
@@ -61,6 +65,9 @@ public class ExportImportTest {
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
||||
|
||||
trackPoints.clear();
|
||||
trackPoints.addAll(Arrays.asList(track.second));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Waypoint waypoint = new Waypoint(track.second[i].getLocation());
|
||||
waypoint.setName("the waypoint " + i);
|
||||
@@ -96,16 +103,13 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(context, outputStream);
|
||||
|
||||
System.out.println(outputStream.toString());
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
|
||||
@@ -115,7 +119,6 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
//TODO assertEquals(track.getTrackPoints(), importedTrack.getTrackPoints());
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
@@ -124,14 +127,42 @@ public class ExportImportTest {
|
||||
// 2. waypoints
|
||||
assertWaypoints();
|
||||
|
||||
//TODO Check absolute time of trackpoints
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, false, false);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
trackExporter.writeTrack(context, outputStream);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. waypoints
|
||||
assertWaypoints();
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(true, true, true);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -168,8 +199,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -185,7 +215,6 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(trackImported);
|
||||
//TODO assertEquals(track.getTrackPoints(), trackImported.getTrackPoints());
|
||||
assertEquals(track.getCategory(), trackImported.getCategory());
|
||||
assertEquals(track.getDescription(), trackImported.getDescription());
|
||||
assertEquals(track.getName(), trackImported.getName());
|
||||
@@ -196,7 +225,8 @@ public class ExportImportTest {
|
||||
// 2. waypoints
|
||||
assertWaypoints();
|
||||
|
||||
//TODO Check absolute time of trackpoints
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, true, true);
|
||||
}
|
||||
|
||||
private void assertWaypoints() {
|
||||
@@ -217,4 +247,33 @@ public class ExportImportTest {
|
||||
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
TrackPoint importedTrackPoint = importedTrackPoints.get(i);
|
||||
|
||||
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime(), 0.01);
|
||||
|
||||
// TODO Not exported for GPX/KML
|
||||
// assertEquals(trackPoint.getAccuracy(), importedTrackPoint.getAccuracy(), 0.01);
|
||||
|
||||
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
|
||||
if (verifyHeartrate) {
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
if (verifyCadence) {
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
}
|
||||
if (verifyPower) {
|
||||
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -21,6 +21,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
|
||||
|
||||
/**
|
||||
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
||||
*
|
||||
|
||||
+13
-11
@@ -172,7 +172,7 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// when
|
||||
service.pauseCurrentTrack();
|
||||
@@ -182,7 +182,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
@@ -201,14 +201,14 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Assert.assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
|
||||
// then
|
||||
Assert.assertTrue(service.isRecording());
|
||||
@@ -294,7 +294,7 @@ public class TrackRecordingServiceTest {
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
newTrackPoint(service);
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
@@ -331,18 +331,18 @@ public class TrackRecordingServiceTest {
|
||||
addTrack(dummyTrack, isRecording);
|
||||
}
|
||||
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
insertLocation(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private static void insertLocation(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
@@ -350,7 +350,9 @@ public class TrackRecordingServiceTest {
|
||||
location.setSpeed(speed);
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertLocation(location);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
//TODO Needed?
|
||||
Thread.sleep(200);
|
||||
|
||||
+35
-82
@@ -72,14 +72,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||
//Disable executorService to not insert locations from GPS via LocationManager
|
||||
service.enableLocationExecutor(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Reset service (if some previous test failed)
|
||||
service.enableLocationExecutor(true);
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
@@ -95,12 +92,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -112,29 +109,6 @@ public class TrackRecordingServiceTestLocation {
|
||||
Assert.assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_movingInaccurate() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.3, 35.0, Long.MAX_VALUE, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 99.0, 35.0, Long.MAX_VALUE, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(45.0, trackPoints.get(0).getLatitude(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||
@@ -142,12 +116,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.000005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 5, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -184,12 +158,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -210,12 +184,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 6, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -253,12 +227,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
});
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -282,11 +256,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.1, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.2, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.1, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.2, 35.0, 5, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -303,25 +277,4 @@ public class TrackRecordingServiceTestLocation {
|
||||
Assert.assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
|
||||
Assert.assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_firstTrackPointInvalid() throws Exception {
|
||||
// given
|
||||
long trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 0, 0);
|
||||
service.insertLocation(TrackPoint.createPause().getLocation());
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
Assert.assertEquals(1, trackPoints.size());
|
||||
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -215,7 +216,9 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setSpeed(10);
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
service.insertLocation(location);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
service.insertWaypoint(null, null, null, null);
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HandlerServerTest {
|
||||
|
||||
@Mock
|
||||
private Context context;
|
||||
|
||||
@Mock
|
||||
private HandlerServer.HandlerServerInterface server;
|
||||
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
private HandlerServer subject;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new HandlerServer(locationHandler, server);
|
||||
subject.start(context);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
subject.stop(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(null, null, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendTrackPoint() throws InterruptedException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
int accuracy = 50;
|
||||
|
||||
// when
|
||||
subject.sendTrackPoint(trackPoint, accuracy);
|
||||
|
||||
// then
|
||||
Thread.sleep(10); // Wait for executor service
|
||||
verify(server).newTrackPoint(trackPoint, accuracy);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LocationHandlerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
|
||||
@InjectMocks
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onStart(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a valid location changed in LocationHandler -> newTrackPoint service method is called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_okay() {
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* When location changed in LocationHandler with bad location -> newTrackPoint service method is not called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_badLocation() {
|
||||
// given
|
||||
float latitude = 91f;
|
||||
|
||||
// when
|
||||
// bad latitude
|
||||
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* When location changed in LocationHandler with poor accuracy -> newTrackPoint service method is not called.
|
||||
*/
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
|
||||
// when
|
||||
// poor latitude
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
// no newTrackPoint called
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLocationChanged_movingInaccurate() {
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45.0, 35.0, 5, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.1, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.2, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45.3, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location with parameters and returns the Location object.
|
||||
*/
|
||||
private static Location createLocation(double latitude, double longitude, float accuracy, long speed, long time) {
|
||||
Location location = new Location("gps");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setSpeed(speed);
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
return location;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ExportTrackFileFormat_invalid() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ExportTrackFileFormat_noValue() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,7 @@ public class StringUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_timezone() {
|
||||
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01Z", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01+00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||
assertGetTime("2010-05-04T03:02:01-00:00", 2010, 5, 4, 3, 2, 1, 0);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UUIDUtilsTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
byte[] bytes = UUIDUtils.toBytes(uuid);
|
||||
UUID output = UUIDUtils.fromBytes(bytes);
|
||||
|
||||
Assert.assertEquals(uuid, output);
|
||||
}
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class AggregatedStatisticsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Create a TrackStatistics object.
|
||||
*
|
||||
* @param totalDistance distance in meters.
|
||||
* @param totalTime total time in milliseconds.
|
||||
* @return TrackStatistics object.
|
||||
*/
|
||||
private static Track createTrack(Context context, long totalDistance, long totalTime, String category) {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime_ms(1000L); // Resulting start time
|
||||
statistics.setStopTime_ms(1000L + totalTime);
|
||||
statistics.setTotalTime(totalTime);
|
||||
statistics.setMovingTime(totalTime);
|
||||
statistics.setTotalDistance(totalDistance);
|
||||
statistics.setTotalElevationGain(50.0);
|
||||
statistics.setMaxSpeed(50.0); // Resulting max speed
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||
|
||||
Track track = new Track();
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
track.setCategory(category);
|
||||
track.setTrackStatistics(statistics);
|
||||
return track;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
Track track = createTrack(context, totalDistance, totalTime, biking);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
// then
|
||||
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_mountainBiking() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String mountainBiking = context.getString(R.string.activity_type_mountain_biking);
|
||||
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(aggregatedStatistics.get(mountainBiking));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_trailRunning() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String trailRunning = context.getString(R.string.activity_type_trail_running);
|
||||
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(aggregatedStatistics.get(trailRunning));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_twoBikingTracks() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
Track[] tracks = new Track[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
tracks[i] = createTrack(context, totalDistance, totalTime, biking);
|
||||
}
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||
|
||||
// then
|
||||
Assert.assertEquals(1, aggregatedStatistics.getCount());
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime * 2L, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_threeDifferentTracks() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
long totalTime = 2400000;
|
||||
Track[] tracks = new Track[]{
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
createTrack(context, totalDistance, totalTime, walking)
|
||||
};
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
|
||||
|
||||
// then
|
||||
Assert.assertEquals(3, aggregatedStatistics.getCount());
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(running));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(walking));
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAggregate_severalTracksWithSeveralActivities() {
|
||||
// given
|
||||
// 10km in 40 minutes.
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
String driving = context.getString(R.string.activity_type_driving);
|
||||
Track[] tracks = new Track[]{
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
createTrack(context, totalDistance, totalTime, walking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
createTrack(context, totalDistance, totalTime, walking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, driving),
|
||||
};
|
||||
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
for (Track track : tracks) {
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
}
|
||||
|
||||
// then
|
||||
// 4 sports.
|
||||
Assert.assertEquals(4, aggregatedStatistics.getCount());
|
||||
|
||||
// There is a map for every sport.
|
||||
Assert.assertNotNull(aggregatedStatistics.get(biking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(running));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(walking));
|
||||
Assert.assertNotNull(aggregatedStatistics.get(driving));
|
||||
|
||||
// Number of tracks by sport.
|
||||
Assert.assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
|
||||
Assert.assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
|
||||
Assert.assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
|
||||
Assert.assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
|
||||
|
||||
// Biking.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime * 5, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Running.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Walking.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Driving.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
|
||||
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
Assert.assertEquals(totalTime, statistics2.getMovingTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,39 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto">
|
||||
<!-- Permission to initialize services -->
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
@@ -26,36 +56,60 @@ limitations under the License.
|
||||
android:label="${applicationName}"
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<!-- Activities -->
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackDeleteActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
android:theme="@style/TrackDeleteActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="@string/menu_help" />
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".io.file.importer.ImportActivity"
|
||||
android:theme="@style/ThemeCustomTransparent">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ATTACH_DATA" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
android:theme="@style/ThemeCustomTransparentActivity">
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/gpx" />
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
<data android:mimeType="application/vnd.google-earth.gpx" />
|
||||
<data android:mimeType="application/vnd.google-earth.gpx+xml" />
|
||||
<data android:scheme="file" />
|
||||
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*kmz" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*kml" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
<data
|
||||
android:scheme="content"
|
||||
android:pathPattern="*gpx" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerDetailActivity" />
|
||||
<activity android:name=".MarkerDetailActivity">
|
||||
<intent-filter>
|
||||
<action android:name="de.dennisguse.opentracks.MarkerDetails"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerEditActivity" />
|
||||
|
||||
@@ -78,9 +132,9 @@ limitations under the License.
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".TrackDetailActivity"
|
||||
android:screenOrientation="userPortrait" />
|
||||
<activity android:name=".TrackRecordedActivity" />
|
||||
|
||||
<activity android:name=".TrackRecordingActivity" />
|
||||
|
||||
<activity android:name=".TrackEditActivity" />
|
||||
|
||||
@@ -94,12 +148,29 @@ limitations under the License.
|
||||
android:value=".SearchListActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AggregatedStatisticsActivity" />
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".io.file.exporter.ExportActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
android:theme="@style/ThemeCustomTransparentActivity" />
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
|
||||
<activity android:name=".ShowOnMapProxyActivity$KMZ">
|
||||
<intent-filter android:label="@string/open_track_as_KMZ">
|
||||
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ShowOnMapProxyActivity$GPX">
|
||||
<intent-filter android:label="@string/open_track_as_GPX">
|
||||
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name=".content.provider.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
@@ -124,32 +195,4 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
</application>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
<!-- Permissions to record locations -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
</manifest>
|
||||
|
||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.ViewUtils;
|
||||
|
||||
public class AboutActivity extends AbstractActivity {
|
||||
|
||||
@@ -27,6 +29,8 @@ public class AboutActivity extends AbstractActivity {
|
||||
|
||||
TextView textURL = findViewById(R.id.about_app_url);
|
||||
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
||||
|
||||
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
protected int getLayoutResId() {
|
||||
|
||||
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
||||
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and
|
||||
* {@link SearchListActivity}:
|
||||
* <p>
|
||||
* - share track <br>
|
||||
@@ -62,23 +62,22 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
||||
@Override
|
||||
public void onConfirmDeleteDone(long[] trackIds) {
|
||||
boolean stopRecording = false;
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
stopRecording = true;
|
||||
} else {
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
for (long trackId : trackIds) {
|
||||
if (trackId == recordingTrackId) {
|
||||
stopRecording = true;
|
||||
break;
|
||||
}
|
||||
|
||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
for (long trackId : trackIds) {
|
||||
if (trackId == recordingTrackId) {
|
||||
stopRecording = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopRecording) {
|
||||
getTrackRecordingServiceConnection().stopRecording(this, false);
|
||||
}
|
||||
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
|
||||
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
|
||||
startActivityForResult(intent, DELETE_REQUEST_CODE);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
||||
|
||||
public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||
|
||||
private ListView listView;
|
||||
private AggregatedStatisticsAdapter adapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
listView = findViewById(R.id.aggregated_stats_list);
|
||||
listView.setEmptyView(findViewById(R.id.aggregated_stats_empty_view));
|
||||
|
||||
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||
viewModel.getAggregatedStats().observe(this, aggregatedStatistics -> {
|
||||
if (aggregatedStatistics != null) {
|
||||
adapter = new AggregatedStatisticsAdapter(getApplicationContext(), aggregatedStatistics);
|
||||
listView.setAdapter(adapter);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.aggregated_stats;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
* Async Task to delete tracks.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private final long[] trackIds;
|
||||
private final Context context;
|
||||
private TrackDeleteActivity deleteActivity;
|
||||
// true if the AsyncTask has completed
|
||||
private boolean completed;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param deleteActivity the activity currently associated with this task
|
||||
* @param trackIds the track ids to delete. To delete all, set to size 1 with
|
||||
* trackIds[0] == -1L
|
||||
*/
|
||||
public DeleteAsyncTask(TrackDeleteActivity deleteActivity, long[] trackIds) {
|
||||
this.deleteActivity = deleteActivity;
|
||||
this.trackIds = trackIds;
|
||||
context = deleteActivity.getApplicationContext();
|
||||
completed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current activity associated with this AyncTask.
|
||||
*
|
||||
* @param deleteActivity the current activity, can be null
|
||||
*/
|
||||
public void setActivity(TrackDeleteActivity deleteActivity) {
|
||||
this.deleteActivity = deleteActivity;
|
||||
if (completed && deleteActivity != null) {
|
||||
deleteActivity.onAsyncTaskCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (deleteActivity != null) {
|
||||
deleteActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
} else {
|
||||
for (long id : trackIds) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
contentProviderUtils.deleteTrack(context, id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (deleteActivity != null) {
|
||||
deleteActivity.onAsyncTaskCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,20 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.dennisguse.opentracks.util.ViewUtils;
|
||||
|
||||
public class HelpActivity extends AbstractActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.help;
|
||||
|
||||
@@ -109,12 +109,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
|
||||
@Override
|
||||
public void onDeleteMarkerDone() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +120,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
@@ -59,13 +58,14 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String BUNDLE_PHOTO_URI = "photo_uri";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private long trackId;
|
||||
private long markerId;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
private Waypoint waypoint;
|
||||
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
@@ -80,15 +80,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
private EditText waypointDescription;
|
||||
private ImageView waypointPhoto;
|
||||
private ImageView waypointDeletePhotoBtn;
|
||||
private Button done;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|
||||
@@ -101,26 +99,65 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
||||
|
||||
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
||||
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (waypoint != null && waypoint.hasPhoto())
|
||||
waypoint.setPhotoUrl(null);
|
||||
waypointPhoto.setImageBitmap(null);
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
waypointDeletePhotoBtn.setOnClickListener(v -> {
|
||||
if (waypoint != null && waypoint.hasPhoto()) {
|
||||
waypoint.setPhotoUrl(null);
|
||||
}
|
||||
waypointPhoto.setImageBitmap(null);
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
});
|
||||
|
||||
Button cancel = findViewById(R.id.marker_edit_cancel);
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
cancel.setOnClickListener(v -> finish());
|
||||
|
||||
final boolean isNewMarker = markerId == -1L;
|
||||
|
||||
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||
Button done = findViewById(R.id.marker_edit_done);
|
||||
done.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
|
||||
done.setOnClickListener(v -> {
|
||||
if (isNewMarker) {
|
||||
addMarker();
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
finish();
|
||||
});
|
||||
done = findViewById(R.id.marker_edit_done);
|
||||
updateUiByMarkerId();
|
||||
|
||||
if (isNewMarker) {
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber));
|
||||
waypointName.selectAll();
|
||||
waypointMarkerType.setText("");
|
||||
waypointDescription.setText("");
|
||||
} else {
|
||||
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
waypointName.setText(waypoint.getName());
|
||||
waypointMarkerType.setText(waypoint.getCategory());
|
||||
waypointDescription.setText(waypoint.getDescription());
|
||||
if (waypoint.hasPhoto()) {
|
||||
photoUri = waypoint.getPhotoURI();
|
||||
}
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
|
||||
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
if (photoUri != null) {
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,6 +166,12 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(BUNDLE_PHOTO_URI, photoUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@@ -198,54 +241,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the marker id.
|
||||
*/
|
||||
private void updateUiByMarkerId() {
|
||||
final boolean newMarker = markerId == -1L;
|
||||
|
||||
setTitle(newMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||
done.setText(newMarker ? R.string.generic_add : R.string.generic_save);
|
||||
done.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (newMarker) {
|
||||
addMarker();
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (newMarker) {
|
||||
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
waypointName.setText(getString(R.string.marker_name_format, nextWaypointNumber));
|
||||
waypointName.selectAll();
|
||||
waypointMarkerType.setText("");
|
||||
waypointDescription.setText("");
|
||||
} else {
|
||||
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
waypointName.setText(waypoint.getName());
|
||||
waypointMarkerType.setText(waypoint.getCategory());
|
||||
waypointDescription.setText(waypoint.getDescription());
|
||||
if (waypoint.hasPhoto()) {
|
||||
photoUri = waypoint.getPhotoURI();
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and hide/shows all buttons/options about marker photo options.
|
||||
*
|
||||
@@ -272,13 +267,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
*
|
||||
* @param uri the uri photo.
|
||||
*/
|
||||
private void setWaypointImageView(Uri uri) {
|
||||
private void setWaypointImageView(@NonNull Uri uri) {
|
||||
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
||||
FileDescriptor fd = pfd.getFileDescriptor();
|
||||
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
||||
waypointPhoto.setImageBitmap(bitmap);
|
||||
hideAndShowOptions();
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
@@ -295,9 +290,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker.
|
||||
*/
|
||||
private void addMarker() {
|
||||
trackRecordingServiceConnection.addMarker(this,
|
||||
waypointName.getText().toString(),
|
||||
@@ -306,9 +298,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a marker.
|
||||
*/
|
||||
private void saveMarker() {
|
||||
waypoint.setName(waypointName.getText().toString());
|
||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -82,12 +80,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MarkerListActivity.this.invalidateOptionsMenu();
|
||||
}
|
||||
});
|
||||
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -134,13 +127,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
listView = findViewById(R.id.marker_list);
|
||||
listView.setEmptyView(findViewById(R.id.marker_list_empty));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
||||
startActivity(intent);
|
||||
}
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
||||
startActivity(intent);
|
||||
});
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
@@ -277,8 +267,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
int size = listView.getCount();
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int i = 0; i < listView.getCount(); i++) {
|
||||
listView.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
@@ -153,12 +151,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
arrayAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> arrayAdapter.notifyDataSetChanged());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -208,20 +201,17 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
ListView listView = findViewById(R.id.search_list);
|
||||
listView.setAdapter(arrayAdapter);
|
||||
listView.setEmptyView(findViewById(R.id.search_list_empty));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
|
||||
if (markerId != null) {
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
|
||||
if (markerId != null) {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||
} else {
|
||||
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(intent);
|
||||
});
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
handleIntent(getIntent());
|
||||
@@ -346,12 +336,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
||||
|
||||
// Use the UI thread to display the results
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
arrayAdapter.clear();
|
||||
arrayAdapter.addAll(displayResults);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
arrayAdapter.clear();
|
||||
arrayAdapter.addAll(displayResults);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,12 +435,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
@Override
|
||||
public void onDeleteMarkerDone() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> handleIntent(getIntent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -463,11 +445,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
@Override
|
||||
protected void onDeleted() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
});
|
||||
runOnUiThread(() -> handleIntent(getIntent()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
|
||||
/**
|
||||
* Used to convert IntentDashboardUtils.startDashboard-requests into {@link TrackFileFormat}.
|
||||
*/
|
||||
public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
|
||||
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
protected ShowOnMapProxyActivity(TrackFileFormat trackFileFormat) {
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
}
|
||||
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
long[] trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
|
||||
|
||||
showTrackTrackfileFormat(this, trackFileFormat, trackIds);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app) as KMZ.
|
||||
*
|
||||
* @param context the context
|
||||
* @param trackIds the track ids
|
||||
*/
|
||||
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, long[] trackIds) {
|
||||
if (trackIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat);
|
||||
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
context.startActivity(Intent.createChooser(intent, context.getString(R.string.open_track_as_trackfileformat, trackFileFormat.getExtension())));
|
||||
}
|
||||
|
||||
public static class KMZ extends ShowOnMapProxyActivity {
|
||||
public KMZ() {
|
||||
super(TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GPX extends ShowOnMapProxyActivity {
|
||||
public GPX() {
|
||||
super(TrackFileFormat.GPX);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import android.app.Application;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -25,6 +27,10 @@ public class Startup extends Application {
|
||||
|
||||
//Set default values of preferences on first start.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
|
||||
//In debug builds: show thread and VM warnings.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
|
||||
/**
|
||||
* Interface for communication between activities that use {@link de.dennisguse.opentracks.content.TrackDataHub} and their fragments that need thi data hub.
|
||||
*/
|
||||
public interface TrackActivityDataHubInterface {
|
||||
TrackDataHub getTrackDataHub();
|
||||
}
|
||||
@@ -51,7 +51,6 @@ public class TrackController {
|
||||
private boolean isPaused;
|
||||
private long totalTime = 0;
|
||||
|
||||
// the timestamp for the toal time
|
||||
private long totalTimeTimestamp = 0;
|
||||
|
||||
private boolean isResumed = false;
|
||||
|
||||
@@ -16,26 +16,26 @@
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.view.View;
|
||||
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
/**
|
||||
* An activity for delete tracks.
|
||||
* An activity for deleting tracks.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TrackDeleteActivity extends Activity {
|
||||
public class TrackDeleteActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
||||
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private long[] trackIds;
|
||||
|
||||
private DeleteAsyncTask deleteAsyncTask;
|
||||
private Thread deleteThread;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -44,53 +44,48 @@ public class TrackDeleteActivity extends Activity {
|
||||
setResult(RESULT_CANCELED);
|
||||
|
||||
Intent intent = getIntent();
|
||||
long[] trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
|
||||
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
|
||||
deleteThread = new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
|
||||
|
||||
Object retained = getLastNonConfigurationInstance();
|
||||
if (retained instanceof DeleteAsyncTask) {
|
||||
deleteAsyncTask = (DeleteAsyncTask) retained;
|
||||
deleteAsyncTask.setActivity(this);
|
||||
} else {
|
||||
deleteAsyncTask = new DeleteAsyncTask(this, trackIds);
|
||||
deleteAsyncTask.execute();
|
||||
}
|
||||
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
|
||||
|
||||
for (long id : trackIds) {
|
||||
if (Thread.interrupted()) {
|
||||
break;
|
||||
}
|
||||
contentProviderUtils.deleteTrack(TrackDeleteActivity.this, id);
|
||||
}
|
||||
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
if (Thread.interrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
runOnUiThread(TrackDeleteActivity.this::onAsyncTaskCompleted);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
deleteAsyncTask.setActivity(null);
|
||||
return deleteAsyncTask;
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
deleteThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
if (id != DIALOG_PROGRESS_ID) {
|
||||
return null;
|
||||
}
|
||||
return DialogUtils.createSpinnerProgressDialog(
|
||||
this, R.string.track_delete_progress_message, new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
deleteAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
deleteThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.track_delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*/
|
||||
public void onAsyncTaskCompleted() {
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
findViewById(R.id.progressbar).setVisibility(View.INVISIBLE);
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(DIALOG_PROGRESS_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
@@ -89,20 +88,12 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
activityType.setAdapter(adapter);
|
||||
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
activityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position))));
|
||||
activityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position)));
|
||||
}
|
||||
});
|
||||
activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, activityType.getText().toString()));
|
||||
}
|
||||
TrackEditActivity.this, activityType.getText().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -116,37 +107,28 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
activityTypeIcon = findViewById(R.id.track_edit_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
description = findViewById(R.id.track_edit_description);
|
||||
description.setText(track.getDescription());
|
||||
|
||||
Button saveButton = findViewById(R.id.track_edit_save);
|
||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||
activityType.getText().toString(), description.getText().toString(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
}
|
||||
saveButton.setOnClickListener(v -> {
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||
activityType.getText().toString(), description.getText().toString(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
});
|
||||
|
||||
Button cancel = findViewById(R.id.track_edit_cancel);
|
||||
@@ -155,12 +137,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
cancel.setVisibility(View.GONE);
|
||||
} else {
|
||||
setTitle(R.string.menu_edit);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
cancel.setOnClickListener(v -> finish());
|
||||
cancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -47,8 +45,6 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
@@ -56,6 +52,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -143,14 +140,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
TrackListActivity.this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -174,15 +168,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
/*
|
||||
* After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
*/
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
||||
}
|
||||
});
|
||||
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
|
||||
|
||||
if (!startGps) {
|
||||
return;
|
||||
@@ -205,8 +192,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_RECORDING_REQUEST, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
@@ -239,23 +225,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Show trackController when search dialog is dismissed
|
||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
trackController.show();
|
||||
}
|
||||
});
|
||||
searchManager.setOnDismissListener(() -> trackController.show());
|
||||
}
|
||||
|
||||
listView = findViewById(R.id.track_list);
|
||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
|
||||
startActivity(newIntent);
|
||||
listView.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
startActivity(newIntent);
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@@ -292,7 +278,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||
|
||||
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
||||
showStartupDialogs();
|
||||
|
||||
requestGPSPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -397,6 +384,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
this.invalidateOptionsMenu();
|
||||
}
|
||||
return true;
|
||||
case R.id.track_list_aggregated_stats:
|
||||
intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_list_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -434,19 +425,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows start up dialogs.
|
||||
*/
|
||||
private void showStartupDialogs() {
|
||||
// If stats_units_key is undefined, set it
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
requestGPSPermissions();
|
||||
}
|
||||
|
||||
private void requestGPSPermissions() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE);
|
||||
@@ -480,7 +458,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, trackIds);
|
||||
IntentDashboardUtils.startDashboard(this, trackIds, false);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
@@ -493,9 +471,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_delete:
|
||||
if (trackIds.length > 1 && trackIds.length == listView.getCount()) {
|
||||
trackIds = new long[]{-1L};
|
||||
}
|
||||
deleteTracks(trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2008 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.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
/**
|
||||
* An activity to show the track detail, record a new track or resumes an existing one.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackRecordedActivity extends AbstractListActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
|
||||
// The following are set in onCreate.
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private TrackDataHub trackDataHub;
|
||||
private ViewPager pager;
|
||||
|
||||
// From intent.
|
||||
private long trackId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
handleIntent(getIntent());
|
||||
|
||||
trackDataHub = new TrackDataHub(this);
|
||||
|
||||
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new StatisticsRecordedFragment();
|
||||
case 1:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(true);
|
||||
default:
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return getString(R.string.track_detail_stats_tab);
|
||||
case 1:
|
||||
return getString(R.string.settings_chart_by_time);
|
||||
case 2:
|
||||
return getString(R.string.settings_chart_by_distance);
|
||||
}
|
||||
return "Unknown Tab";
|
||||
}
|
||||
};
|
||||
pager = findViewById(R.id.track_detail_activity_view_pager);
|
||||
pager.setAdapter(adapter);
|
||||
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
|
||||
tabs.setupWithViewPager(pager);
|
||||
if (savedInstanceState != null) {
|
||||
pager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
trackDataHub.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
|
||||
if (trackId != -1L) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackDataHub.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.track_detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
setTitle(track != null ? track.getName() : "");
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.track_detail_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, false);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_edit:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_delete:
|
||||
deleteTracks(new long[]{trackId});
|
||||
return true;
|
||||
case R.id.track_detail_resume_track:
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
finish();
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
// Not needed.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDeleted() {
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TrackDataHub}.
|
||||
*/
|
||||
@Override
|
||||
public TrackDataHub getTrackDataHub() {
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
|
||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
if (markerId != -1L) {
|
||||
// Use the trackId from the marker
|
||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
trackId = waypoint.getTrackId();
|
||||
}
|
||||
|
||||
if (trackId == -1L) {
|
||||
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void chooseActivityType(String category) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseActivityTypeDone(String iconValue) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
}
|
||||
}
|
||||
+117
-220
@@ -1,19 +1,3 @@
|
||||
/*
|
||||
* Copyright 2008 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.Intent;
|
||||
@@ -27,10 +11,10 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
@@ -38,15 +22,14 @@ import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -58,13 +41,13 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDetailActivity extends AbstractListActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||
//This mode of communication should be removed anyhow.
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
public static final String EXTRA_RECORDING_REQUEST = "request_recording";
|
||||
|
||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||
private static final String TAG = TrackRecordingActivity.class.getSimpleName();
|
||||
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
|
||||
@@ -76,57 +59,37 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private ViewPager pager;
|
||||
private TrackController trackController;
|
||||
|
||||
// From intent
|
||||
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
||||
private long trackId;
|
||||
|
||||
private boolean isRecordingRequest = false;
|
||||
|
||||
// Preferences
|
||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// After binding changes (is available), update the total time in trackController.
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(isRecording(), recordingTrackPaused);
|
||||
}
|
||||
});
|
||||
|
||||
if (recordingTrackId == -1L) {
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
if (!isRecordingRequest) {
|
||||
return;
|
||||
}
|
||||
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
||||
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
if (!service.isRecording()) {
|
||||
// Starts or resumes a track.
|
||||
int msg;
|
||||
if (trackId == -1L) {
|
||||
// trackId isn't initialized -> leads a new recording.
|
||||
trackId = service.startNewTrack();
|
||||
recordingTrackId = trackId;
|
||||
msg = R.string.track_detail_record_success;
|
||||
} else {
|
||||
// trackId is initialized -> resumes the track.
|
||||
recordingTrackId = trackId;
|
||||
service.resumeTrack(trackId);
|
||||
msg = R.string.track_detail_resume_success;
|
||||
}
|
||||
|
||||
// A recording track is on.
|
||||
Toast.makeText(TrackDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.update(true, false);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
|
||||
isRecordingRequest = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -134,43 +97,34 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||
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)) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.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)) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
|
||||
setFullscreenPolicy();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TrackDetailActivity.this.invalidateOptionsMenu();
|
||||
boolean isRecording = isRecording();
|
||||
trackController.update(isRecording, recordingTrackPaused);
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
TrackRecordingActivity.this.invalidateOptionsMenu();
|
||||
trackController.update(true, recordingTrackPaused);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem resumeMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -192,58 +146,40 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
trackRecordingServiceConnection.stopRecording(TrackDetailActivity.this, true);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
updateMenuItems(true);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
handleIntent(getIntent());
|
||||
|
||||
trackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
||||
if (savedInstanceState != null) {
|
||||
//Activity was recreated.
|
||||
trackId = savedInstanceState.getLong(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
} else {
|
||||
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT && contentProviderUtils.getTrack(trackId) == null) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackDataHub = new TrackDataHub(this);
|
||||
|
||||
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 1:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(true);
|
||||
default: //0
|
||||
return new StatsFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return getString(R.string.track_detail_stats_tab);
|
||||
case 1:
|
||||
return getString(R.string.settings_chart_by_time);
|
||||
case 2:
|
||||
return getString(R.string.settings_chart_by_distance);
|
||||
}
|
||||
return "Unknown Tab";
|
||||
}
|
||||
};
|
||||
pager = findViewById(R.id.track_detail_activity_view_pager);
|
||||
pager.setAdapter(adapter);
|
||||
pager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), 1));
|
||||
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
|
||||
tabs.setupWithViewPager(pager);
|
||||
if (savedInstanceState != null) {
|
||||
@@ -257,12 +193,12 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
setFullscreenPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -274,8 +210,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
@@ -284,6 +219,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
}
|
||||
|
||||
private void setFullscreenPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -304,10 +249,23 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
|
||||
if (trackId != -1L) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
||||
} else {
|
||||
startRecording();
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||
* But when the binding occurs, the callback will get invoked.
|
||||
*/
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
bindChangedCallback.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||
outState.putLong(EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -324,32 +282,17 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
trackDataHub.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.track_detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntent(intent);
|
||||
return R.layout.track_record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||
getMenuInflater().inflate(R.menu.track_record, menu);
|
||||
|
||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -370,13 +313,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, true);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
@@ -388,13 +326,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_delete:
|
||||
deleteTracks(new long[]{trackId});
|
||||
return true;
|
||||
case R.id.track_detail_resume_track:
|
||||
isRecordingRequest = true;
|
||||
startRecording();
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -404,81 +335,21 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
return trackRecordingServiceConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDeleted() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TrackDataHub}.
|
||||
*/
|
||||
@Override
|
||||
public TrackDataHub getTrackDataHub() {
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
isRecordingRequest = intent.getBooleanExtra(EXTRA_RECORDING_REQUEST, false);
|
||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
if (markerId != -1L) {
|
||||
// Use the trackId from the marker
|
||||
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
|
||||
if (waypoint == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
trackId = waypoint.getTrackId();
|
||||
if (trackId == -1L) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackId == -1L) {
|
||||
return;
|
||||
}
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
// Use the last track if markerId is not set
|
||||
if (markerId == -1L) {
|
||||
track = contentProviderUtils.getLastTrack();
|
||||
if (track != null) {
|
||||
trackId = track.getId();
|
||||
return;
|
||||
}
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu items.
|
||||
*/
|
||||
private void updateMenuItems(boolean isPaused) {
|
||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
||||
shareMenuItem.setVisible(!isRecording());
|
||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
String title;
|
||||
if (isRecording()) {
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
resumeMenuItem.setVisible(false);
|
||||
} else {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
title = track != null ? track.getName() : "";
|
||||
// Only visible if there aren't other tracks that is recording.
|
||||
resumeMenuItem.setVisible(recordingTrackId == -1L);
|
||||
}
|
||||
setTitle(title);
|
||||
insertMarkerMenuItem.setVisible(!isPaused);
|
||||
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
||||
}
|
||||
|
||||
public void chooseActivityType(String category) {
|
||||
@@ -492,18 +363,44 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
}
|
||||
|
||||
private boolean isRecording() {
|
||||
return trackId == recordingTrackId;
|
||||
}
|
||||
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private void startRecording() {
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
|
||||
super(fm, behavior);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||
* But when the binding occurs, the callback will get invoked.
|
||||
*/
|
||||
bindChangedCallback.run();
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new StatisticsRecordingFragment();
|
||||
case 1:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(true);
|
||||
default:
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return getString(R.string.track_detail_stats_tab);
|
||||
case 1:
|
||||
return getString(R.string.settings_chart_by_time);
|
||||
case 2:
|
||||
return getString(R.string.settings_chart_by_distance);
|
||||
default:
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||
|
||||
public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
|
||||
private final AggregatedStatistics aggregatedStatistics;
|
||||
private final Context context;
|
||||
|
||||
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
|
||||
this.context = context;
|
||||
this.aggregatedStatistics = aggregatedStatistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (aggregatedStatistics != null) {
|
||||
return aggregatedStatistics.getCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregatedStatistics.AggregatedStatistic getItem(int position) {
|
||||
return aggregatedStatistics.getItem(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// Not important because data are not database register but cooked data.
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
AggregatedStatistics.AggregatedStatistic aggregatedStats = getItem(position);
|
||||
ViewHolder viewHolder;
|
||||
|
||||
if (isSpeedSport(position)) {
|
||||
viewHolder = new ViewSpeedHolder();
|
||||
} else {
|
||||
viewHolder = new ViewPaceHolder();
|
||||
}
|
||||
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
||||
viewHolder.setValues(convertView, getIcon(position), getName(position), aggregatedStats);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private boolean isSpeedSport(int position) {
|
||||
String category = aggregatedStatistics.getSportName(position);
|
||||
return TrackIconUtils.isSpeedIcon(context, category);
|
||||
}
|
||||
|
||||
private int getIcon(int position) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistics.getSportName(position));
|
||||
return TrackIconUtils.getIconDrawable(iconValue);
|
||||
}
|
||||
|
||||
private String getName(int position) {
|
||||
return aggregatedStatistics.getSportName(position);
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
protected ImageView sportIcon;
|
||||
protected TextView typeLabel;
|
||||
protected TextView numTracks;
|
||||
protected TextView distance;
|
||||
protected TextView distanceUnit;
|
||||
protected TextView time;
|
||||
protected boolean metricsUnits;
|
||||
protected boolean reportSpeed;
|
||||
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, name);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
|
||||
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
||||
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
||||
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
||||
distance = view.findViewById(R.id.aggregated_stats_distance);
|
||||
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
|
||||
time = view.findViewById(R.id.aggregated_stats_time);
|
||||
|
||||
sportIcon.setImageResource(iconDrawable);
|
||||
typeLabel.setText(name);
|
||||
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStats.getCountTracks())));
|
||||
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStats.getTrackStatistics().getTotalDistance(), metricsUnits);
|
||||
distance.setText(parts.first);
|
||||
distanceUnit.setText(parts.second);
|
||||
|
||||
time.setText(StringUtils.formatElapsedTime(aggregatedStats.getTrackStatistics().getMovingTime()));
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewSpeedHolder extends ViewHolder {
|
||||
private TextView avgSpeed;
|
||||
private TextView avgSpeedUnit;
|
||||
private TextView avgSpeedLabel;
|
||||
private TextView maxSpeed;
|
||||
private TextView maxSpeedUnit;
|
||||
private TextView maxSpeedLabel;
|
||||
|
||||
@Override
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||
|
||||
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgSpeed.setText(parts.first);
|
||||
avgSpeedUnit.setText(parts.second);
|
||||
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxSpeed.setText(parts.first);
|
||||
maxSpeedUnit.setText(parts.second);
|
||||
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewPaceHolder extends ViewHolder {
|
||||
private TextView avgPace;
|
||||
private TextView avgPaceUnit;
|
||||
private TextView avgPaceLabel;
|
||||
private TextView maxPace;
|
||||
private TextView maxPaceUnit;
|
||||
private TextView maxPaceLabel;
|
||||
|
||||
@Override
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||
|
||||
avgPace = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||
avgPaceUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||
avgPaceLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||
|
||||
maxPace = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||
maxPaceUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgPace.setText(parts.first);
|
||||
avgPaceUnit.setText(parts.second);
|
||||
avgPaceLabel.setText(R.string.stats_average_moving_pace);
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxPace.setText(parts.first);
|
||||
maxPaceUnit.setText(parts.second);
|
||||
maxPaceLabel.setText(R.string.stats_fastest_pace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,28 +108,25 @@ public class SearchEngine {
|
||||
/**
|
||||
* Comparator for scored results.
|
||||
*/
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
|
||||
@Override
|
||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finally, use arbitrary ordering, by ID.
|
||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||
long idDiff = id2 - id1;
|
||||
return Long.signum(idDiff);
|
||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = (r1, r2) -> {
|
||||
// Score ordering.
|
||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||
if (scoreDiff != 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
// Make tracks come before waypoints.
|
||||
if (r1.waypoint != null && r2.track != null) {
|
||||
return 1;
|
||||
} else if (r1.track != null && r2.waypoint != null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finally, use arbitrary ordering, by ID.
|
||||
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
|
||||
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
|
||||
long idDiff = id2 - id1;
|
||||
return Long.signum(idDiff);
|
||||
};
|
||||
|
||||
private final ContentProviderUtils providerUtils;
|
||||
|
||||
@@ -144,12 +144,9 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
PreferencesUtils.register(context, this);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -178,16 +175,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
|
||||
public void loadTrack(final long trackId) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (trackId == selectedTrackId) {
|
||||
Log.i(TAG, "Not reloading track " + trackId);
|
||||
return;
|
||||
}
|
||||
selectedTrackId = trackId;
|
||||
loadDataForAll();
|
||||
runInHandlerThread(() -> {
|
||||
if (trackId == selectedTrackId) {
|
||||
Log.i(TAG, "Not reloading track " + trackId);
|
||||
return;
|
||||
}
|
||||
selectedTrackId = trackId;
|
||||
loadDataForAll();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,13 +191,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @param trackDataListener the track data listener
|
||||
*/
|
||||
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -214,12 +205,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @param trackDataListener the track data listener
|
||||
*/
|
||||
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.unregisterTrackDataListener(trackDataListener);
|
||||
}
|
||||
});
|
||||
runInHandlerThread(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,15 +224,12 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
}
|
||||
runInHandlerThread(() -> {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -289,7 +272,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
if (isOnlyListener) {
|
||||
resetSamplingState();
|
||||
}
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.emptySet();
|
||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
public class Track {
|
||||
|
||||
private long id = -1L;
|
||||
private UUID uuid = UUID.randomUUID();
|
||||
|
||||
private String name = "";
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
@@ -46,6 +50,14 @@ public class Track {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface TracksColumns extends BaseColumns {
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
// Columns
|
||||
String UUID = "uuid";
|
||||
String NAME = "name"; // track name
|
||||
String DESCRIPTION = "description"; // track description
|
||||
String CATEGORY = "category"; // track activity type
|
||||
@@ -71,5 +72,9 @@ public interface TracksColumns extends BaseColumns {
|
||||
+ MINELEVATION + " FLOAT, " // min elevation
|
||||
+ MAXELEVATION + " FLOAT, " // max elevation
|
||||
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
||||
+ ICON + " TEXT)"; // icon
|
||||
+ ICON + " TEXT, " // icon
|
||||
+ UUID + " BLOB)"; // UUID
|
||||
|
||||
String CREATE_TABLE_INDEX = "CREATE UNIQUE INDEX " + TABLE_NAME + "_" + UUID + "_index ON " + TABLE_NAME + "(" + UUID + ")";
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public final class Waypoint {
|
||||
private double length = 0.0;
|
||||
private long duration = 0;
|
||||
private Location location;
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
private String photoUrl = "";
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -30,6 +30,7 @@ public interface WaypointsColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "waypoints";
|
||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
|
||||
String DEFAULT_SORT_ORDER = "_id";
|
||||
|
||||
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
/**
|
||||
* {@link ContentProviderUtils} implementation.
|
||||
@@ -99,6 +100,7 @@ public class ContentProviderUtils {
|
||||
*/
|
||||
public Track createTrack(Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
int uuidIndex = cursor.getColumnIndexOrThrow(TracksColumns.UUID);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
@@ -118,6 +120,9 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(idIndex)) {
|
||||
track.setId(cursor.getLong(idIndex));
|
||||
}
|
||||
if (!cursor.isNull(uuidIndex)) {
|
||||
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
|
||||
}
|
||||
if (!cursor.isNull(nameIndex)) {
|
||||
track.setName(cursor.getString(nameIndex));
|
||||
}
|
||||
@@ -160,9 +165,7 @@ public class ContentProviderUtils {
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all tracks (including waypoints and trackPoints).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void deleteAllTracks(Context context) {
|
||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
||||
@@ -287,6 +290,7 @@ public class ContentProviderUtils {
|
||||
if (track.getId() >= 0) {
|
||||
values.put(TracksColumns._ID, track.getId());
|
||||
}
|
||||
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
||||
values.put(TracksColumns.NAME, track.getName());
|
||||
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||
@@ -910,5 +914,4 @@ public class ContentProviderUtils {
|
||||
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath(), UrlType.WAYPOINTS.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.WAYPOINTS_BY_TRACKID.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +141,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
case WAYPOINTS:
|
||||
return WaypointsColumns.CONTENT_TYPE;
|
||||
case WAYPOINTS_BY_ID:
|
||||
case WAYPOINTS_BY_TRACKID:
|
||||
return WaypointsColumns.CONTENT_ITEMTYPE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
@@ -219,6 +221,10 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
case WAYPOINTS_BY_TRACKID:
|
||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(WaypointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
@@ -382,6 +388,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
WAYPOINTS,
|
||||
WAYPOINTS_BY_ID
|
||||
WAYPOINTS_BY_ID,
|
||||
WAYPOINTS_BY_TRACKID
|
||||
}
|
||||
}
|
||||
|
||||
+51
-5
@@ -1,15 +1,20 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||
|
||||
/**
|
||||
* Database helper for creating and upgrading the database.
|
||||
@@ -17,13 +22,11 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
@VisibleForTesting
|
||||
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
@VisibleForTesting
|
||||
private static final int DATABASE_VERSION = 25;
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
private static final String DATABASE_NAME = "database.db";
|
||||
private static final int DATABASE_VERSION = 26;
|
||||
|
||||
private static final String DATABASE_NAME = "database.db";
|
||||
|
||||
public CustomSQLiteOpenHelper(Context context) {
|
||||
this(context, DATABASE_NAME);
|
||||
@@ -45,6 +48,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
||||
|
||||
db.execSQL(TracksColumns.CREATE_TABLE);
|
||||
db.execSQL(TracksColumns.CREATE_TABLE_INDEX);
|
||||
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
||||
@@ -61,6 +65,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 25:
|
||||
upgradeFrom24to25(db);
|
||||
break;
|
||||
case 26:
|
||||
upgradeFrom25to26(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
@@ -79,6 +86,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 24:
|
||||
downgradeFrom25to24(db);
|
||||
break;
|
||||
case 25:
|
||||
downgradeFrom26to25(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
@@ -107,7 +117,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
private void downgradeFrom24to23(SQLiteDatabase db) {
|
||||
//Not needed as the delete columns did not contain any data
|
||||
//Not needed as the deleted columns did not contain any data
|
||||
db.beginTransaction();
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||
@@ -142,4 +152,40 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void upgradeFrom25to26(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE tracks ADD COLUMN uuid BLOB");
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id"}, null, null, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
|
||||
do {
|
||||
long trackId = cursor.getLong(trackIdIndex);
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
|
||||
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)});
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom26to25(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("DROP INDEX tracks_uuid_index");
|
||||
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, 0, 0, icon FROM tracks_old");
|
||||
db.execSQL("DROP TABLE tracks_old");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
@@ -92,7 +91,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
|
||||
static long[] parseURI(Uri uri) {
|
||||
List<String> uriPaths = uri.getPathSegments();
|
||||
if (uriPaths == null || uriPaths.size() < 2) {
|
||||
if (uriPaths == null || uriPaths.size() < 3) {
|
||||
Log.d(TAG, "URI does not contain any trackIds.");
|
||||
return new long[]{};
|
||||
}
|
||||
@@ -209,16 +208,13 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
||||
}
|
||||
|
||||
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks, null);
|
||||
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks);
|
||||
|
||||
PipeDataWriter pipeDataWriter = new PipeDataWriter<String>() {
|
||||
@Override
|
||||
public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args) {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
PipeDataWriter<String> pipeDataWriter = (PipeDataWriter<String>) (output, uri1, mimeType, opts, args) -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -100,13 +100,4 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
if (cursor == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cursor.getCount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
|
||||
public class SensorData {
|
||||
|
||||
private String sensorAddress;
|
||||
private String sensorName;
|
||||
private final String sensorAddress;
|
||||
private final String sensorName;
|
||||
|
||||
private long timestamp_ms;
|
||||
private final long timestamp_ms;
|
||||
|
||||
SensorData(String sensorAddress, String sensorName) {
|
||||
this(sensorAddress, sensorName, System.currentTimeMillis());
|
||||
|
||||
@@ -25,8 +25,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class Cadence extends SensorData {
|
||||
|
||||
private long crankRevolutionsCount; // UINT32
|
||||
private int crankRevolutionsTime; // UINT16; 1/1024s
|
||||
private final long crankRevolutionsCount; // UINT32
|
||||
private final int crankRevolutionsTime; // UINT16; 1/1024s
|
||||
private float cadence_rpm = INVALID_VALUE_FLOAT;
|
||||
|
||||
public Cadence(String sensorAddress, String sensorName, long crankRevolutionsCount, int crankRevolutionsTime) {
|
||||
@@ -64,13 +64,13 @@ public final class SensorDataCycling {
|
||||
|
||||
public void compute(Cadence previous) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
long timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024 * UnitConversions.S_TO_MS;
|
||||
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
cadence_rpm = INVALID_VALUE_FLOAT;
|
||||
} else {
|
||||
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
||||
float cadence_ms = crankDiff / (float) timeDiff_ms;
|
||||
float cadence_ms = crankDiff / timeDiff_ms;
|
||||
cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public final class SensorDataCycling {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "cadence=" + getCadence_rpm() + "_" + getCrankRevolutionsTime();
|
||||
return "cadence=" + getCadence_rpm() + " time=" + getCrankRevolutionsTime() + " count=" + getCrankRevolutionsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,8 +93,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class Speed extends SensorData {
|
||||
|
||||
private int wheelRevolutionsCount; // UINT16
|
||||
private int wheelRevolutionsTime; // UINT16; 1/1024s
|
||||
private final int wheelRevolutionsCount; // UINT16
|
||||
private final int wheelRevolutionsTime; // UINT16; 1/1024s
|
||||
private float speed_mps = INVALID_VALUE_FLOAT;
|
||||
|
||||
public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
|
||||
@@ -125,7 +125,7 @@ public final class SensorDataCycling {
|
||||
|
||||
public void compute(Speed previous, int wheel_circumference_mm) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
long timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024 * UnitConversions.S_TO_MS;
|
||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
speed_mps = INVALID_VALUE_FLOAT;
|
||||
@@ -140,7 +140,7 @@ public final class SensorDataCycling {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "speed=" + getSpeed_mps() + "_" + getWheelRevolutionsTime();
|
||||
return "speed=" + getSpeed_mps() + " time=" + getWheelRevolutionsTime() + " count=" + getWheelRevolutionsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,8 +154,8 @@ public final class SensorDataCycling {
|
||||
|
||||
public static class CadenceAndSpeed extends SensorData {
|
||||
|
||||
private Cadence cadence;
|
||||
private Speed speed;
|
||||
private final Cadence cadence;
|
||||
private final Speed speed;
|
||||
|
||||
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
|
||||
super(sensorAddress, sensorName);
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
public class SensorDataHeartRate extends SensorData {
|
||||
|
||||
private float heartRate_bpm;
|
||||
private final float heartRate_bpm;
|
||||
|
||||
public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
|
||||
super(name, address);
|
||||
|
||||
@@ -4,9 +4,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public final class SensorDataSet {
|
||||
|
||||
private SensorDataHeartRate heartRate;
|
||||
@@ -31,10 +28,6 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
public void set(SensorData data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data instanceof SensorDataHeartRate) {
|
||||
this.heartRate = (SensorDataHeartRate) data;
|
||||
return;
|
||||
@@ -48,10 +41,6 @@ public final class SensorDataSet {
|
||||
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
||||
return;
|
||||
}
|
||||
if (data instanceof SensorDataCycling.CadenceAndSpeed) {
|
||||
set(((SensorDataCycling.CadenceAndSpeed) data).getCadence());
|
||||
set(((SensorDataCycling.CadenceAndSpeed) data).getSpeed());
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
@@ -90,12 +90,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
if (metricUnits != chartView.getMetricUnits()) {
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,12 +103,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
chartView.applyReportSpeed();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
chartView.requestLayout();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -214,12 +208,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
||||
pendingPoints.clear();
|
||||
chartView.reset();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
chartView.resetScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -286,7 +277,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -43,7 +42,7 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param trackIds list of track ids to delete. To delete all, set to size 1 with trackIds[0] == -1L
|
||||
* @param trackIds list of track ids to delete.
|
||||
*/
|
||||
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -69,17 +68,14 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
|
||||
|
||||
int titleId;
|
||||
int messageId;
|
||||
titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
|
||||
messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
|
||||
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
|
||||
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
|
||||
return DialogUtils.createConfirmationDialog(
|
||||
getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
caller.onConfirmDeleteDone(trackIds);
|
||||
}
|
||||
});
|
||||
getActivity(),
|
||||
titleId,
|
||||
getString(messageId),
|
||||
(dialog, which) -> caller.onConfirmDeleteDone(trackIds)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,12 +85,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
||||
*/
|
||||
public interface ConfirmDeleteCaller {
|
||||
|
||||
/**
|
||||
* Called when confirm delete is done.
|
||||
*
|
||||
* @param trackIds list of track ids to delete. To delete all, set to size 1
|
||||
* with trackIds[0] == -1L
|
||||
*/
|
||||
void onConfirmDeleteDone(long[] trackIds);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -77,21 +76,13 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
|
||||
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
|
||||
}
|
||||
return DialogUtils.createConfirmationDialog(
|
||||
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
caller.onDeleteMarkerDone();
|
||||
}
|
||||
}).start();
|
||||
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||
for (long markerId : markerIds) {
|
||||
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||
}
|
||||
});
|
||||
caller.onDeleteMarkerDone();
|
||||
}).start());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class ExportFinishedDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String TAG = ExportFinishedDialogFragment.class.getSimpleName();
|
||||
|
||||
private static final String EXTRA_DIRECTORY_NAME_KEY = "directory_name";
|
||||
private static final String EXTRA_EXPORTED_TRACK_COUNT = "exported_track_count";
|
||||
private static final String EXTRA_TOTAL_TRACK_COUNT = "";
|
||||
|
||||
private static final String FINISHED_DIALOG_TAG = "finished_dialog_tag";
|
||||
|
||||
private DismissCallback caller;
|
||||
|
||||
public static void showDialog(FragmentManager fragmentManager, String directoryDisplayName, int successCount, int fileCount) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_DIRECTORY_NAME_KEY, directoryDisplayName);
|
||||
bundle.putInt(EXTRA_EXPORTED_TRACK_COUNT, successCount);
|
||||
bundle.putInt(EXTRA_TOTAL_TRACK_COUNT, fileCount);
|
||||
|
||||
ExportFinishedDialogFragment dialogFragment = new ExportFinishedDialogFragment();
|
||||
dialogFragment.setArguments(bundle);
|
||||
dialogFragment.show(fragmentManager, FINISHED_DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
caller = (DismissCallback) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
String directoryDisplayName = getArguments().getString(EXTRA_DIRECTORY_NAME_KEY);
|
||||
int exportedTracksCount = getArguments().getInt(EXTRA_EXPORTED_TRACK_COUNT);
|
||||
int totalTracksCount = getArguments().getInt(EXTRA_TOTAL_TRACK_COUNT);
|
||||
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalTracks = getResources().getQuantityString(R.plurals.tracks, totalTracksCount, totalTracksCount);
|
||||
if (exportedTracksCount == totalTracksCount) {
|
||||
iconId = R.drawable.ic_dialog_success_24dp;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(
|
||||
R.string.export_success, totalTracks, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_error_24dp;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(R.string.export_error, exportedTracksCount, totalTracks, directoryDisplayName);
|
||||
}
|
||||
Dialog alertDialog = new AlertDialog.Builder(getContext()).setCancelable(true)
|
||||
.setIcon(iconId).setMessage(message)
|
||||
.setPositiveButton(R.string.generic_ok, (dialog, arg1) -> {
|
||||
dialog.dismiss();
|
||||
caller.onDismissed();
|
||||
}).setTitle(titleId)
|
||||
.create();
|
||||
|
||||
alertDialog.setCanceledOnTouchOutside(false);
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
caller = null;
|
||||
}
|
||||
|
||||
public interface DismissCallback {
|
||||
void onDismissed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ExportProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
private static final String EXTRA_TRACKFILEFORMAT_KEY = "trackfileformat";
|
||||
|
||||
private static final String TAG = ExportProgressDialogFragment.class.getSimpleName();
|
||||
|
||||
public static final String EXPORT_DIALOG_TAG = "export_dialog_tag";
|
||||
|
||||
private DismissCallback caller;
|
||||
|
||||
private ExportThread exportThread;
|
||||
|
||||
private String directoryDisplayName;
|
||||
|
||||
private int trackExportSuccessCount;
|
||||
private int trackCount;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
public static void showDialog(FragmentManager fragmentManager, TrackFileFormat trackFileFormat, Uri documentFileUri) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_DIRECTORY_URI_KEY, documentFileUri);
|
||||
bundle.putSerializable(EXTRA_TRACKFILEFORMAT_KEY, trackFileFormat);
|
||||
|
||||
ExportProgressDialogFragment dialogFragment = new ExportProgressDialogFragment();
|
||||
dialogFragment.setArguments(bundle);
|
||||
dialogFragment.setRetainInstance(true);
|
||||
dialogFragment.show(fragmentManager, EXPORT_DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// This fragment uses a background thread, so it should not be recreated during activity re-creation.
|
||||
setRetainInstance(true);
|
||||
|
||||
Uri directoryUri = getArguments().getParcelable(EXTRA_DIRECTORY_URI_KEY);
|
||||
TrackFileFormat trackFileFormat = (TrackFileFormat) getArguments().getSerializable(EXTRA_TRACKFILEFORMAT_KEY);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(getContext(), directoryUri);
|
||||
|
||||
directoryDisplayName = FileUtils.getPath(documentFile);
|
||||
|
||||
exportThread = new ExportThread(trackFileFormat, documentFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
caller = (DismissCallback) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progressDialog = DialogUtils.createHorizontalProgressDialog(getContext(),
|
||||
R.string.export_progress_message,
|
||||
dialog -> {
|
||||
exportThread.interrupt();
|
||||
dialog.dismiss();
|
||||
caller.onExportCanceled(directoryDisplayName, trackExportSuccessCount, trackCount);
|
||||
}, directoryDisplayName);
|
||||
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(trackCount);
|
||||
progressDialog.setProgress(Math.min(trackExportSuccessCount, trackCount));
|
||||
|
||||
return progressDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setProgress();
|
||||
if (!exportThread.isAlive()) {
|
||||
exportThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!exportThread.isAlive()) {
|
||||
exportThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
caller = null;
|
||||
}
|
||||
|
||||
private void onProgress(int trackImportSuccessCount, int fileCount) {
|
||||
this.trackExportSuccessCount = trackImportSuccessCount;
|
||||
this.trackCount = fileCount;
|
||||
setProgress();
|
||||
}
|
||||
|
||||
private void setProgress() {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
if (dialog != null) {
|
||||
dialog.setIndeterminate(false);
|
||||
dialog.setMax(trackCount);
|
||||
dialog.setProgress(Math.min(trackExportSuccessCount, trackCount));
|
||||
}
|
||||
}
|
||||
|
||||
private void onExportCompleted(int successCount, int fileCount) {
|
||||
if (caller != null && !isDetached()) {
|
||||
caller.onExportFinished(directoryDisplayName, successCount, fileCount);
|
||||
}
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
private class ExportThread extends Thread {
|
||||
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
private final DocumentFile directory;
|
||||
|
||||
ExportThread(TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
this.directory = directory;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = ExportProgressDialogFragment.this.getContext();
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
if (cursor == null) {
|
||||
onExportCompleted(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
trackCount = cursor.getCount();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
onProgress(i, trackCount);
|
||||
if (Thread.interrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cursor.moveToPosition(i);
|
||||
Track track = contentProviderUtils.createTrack(cursor);
|
||||
if (track != null && exportTrack(context, track)) {
|
||||
trackExportSuccessCount++;
|
||||
}
|
||||
}
|
||||
|
||||
onExportCompleted(trackExportSuccessCount, trackCount);
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean exportTrack(Context context, Track track) {
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
|
||||
|
||||
//TODO Move to helper function
|
||||
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
||||
|
||||
// Overwrite a file if it exists; DocumentFile.createFile() creates a new file appending a suffix if the displayname already exists.
|
||||
DocumentFile file = directory.findFile(fileName);
|
||||
if (file == null) {
|
||||
file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
|
||||
if (trackExporter.writeTrack(context, outputStream)) {
|
||||
return true;
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
Log.e(TAG, "Unable to delete file");
|
||||
}
|
||||
Log.e(TAG, "Unable to export track");
|
||||
return false;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open file " + file.getName(), e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close file output stream", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface DismissCallback {
|
||||
void onExportCanceled(String directoryDisplayName, int successCount, int fileCount);
|
||||
|
||||
void onExportFinished(String directoryDisplayName, int successCount, int fileCount);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 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.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
* A DialogFragment to select a file type, gpx, kml, etc.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class FileTypeDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String FILE_TYPE_DIALOG_TAG = "fileType";
|
||||
private static final String KEY_TITLE_ID = "titleId";
|
||||
private static final String KEY_OPTION_ID = "optionId";
|
||||
|
||||
private FileTypeCaller caller;
|
||||
|
||||
public static void showDialog(FragmentManager fragmentManager, int titleId, int optionId) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(KEY_TITLE_ID, titleId);
|
||||
bundle.putInt(KEY_OPTION_ID, optionId);
|
||||
|
||||
FileTypeDialogFragment fileTypeDialogFragment = new FileTypeDialogFragment();
|
||||
fileTypeDialogFragment.setArguments(bundle);
|
||||
fileTypeDialogFragment.show(fragmentManager, FILE_TYPE_DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
caller = (FileTypeCaller) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context + " must implement " + FileTypeCaller.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
int optionId = getArguments().getInt(KEY_OPTION_ID);
|
||||
final int titleId = getArguments().getInt(KEY_TITLE_ID);
|
||||
|
||||
final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX};
|
||||
String[] choices = new String[trackFileFormats.length];
|
||||
for (int i = 0; i < choices.length; i++) {
|
||||
TrackFileFormat trackFileFormat = trackFileFormats[i];
|
||||
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
||||
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
|
||||
String trackFileFormatDisplayName = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId));
|
||||
choices[i] = getString(optionId, trackFileFormatDisplayName);
|
||||
}
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setNegativeButton(R.string.generic_cancel, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onDismissed();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.generic_ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
int position = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
caller.onFileTypeDone(trackFileFormats[position]);
|
||||
}
|
||||
})
|
||||
.setSingleChoiceItems(choices, 0, null)
|
||||
.setTitle(titleId)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(@NonNull DialogInterface dialog) {
|
||||
onDismissed();
|
||||
}
|
||||
|
||||
private void onDismissed() {
|
||||
dismiss();
|
||||
caller.onDismissed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for caller of this dialog fragment.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface FileTypeCaller {
|
||||
|
||||
/**
|
||||
* Called when file type selection is done.
|
||||
*/
|
||||
void onFileTypeDone(TrackFileFormat trackFileFormat);
|
||||
|
||||
void onDismissed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class ImportFinishedDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String TAG = ImportFinishedDialogFragment.class.getSimpleName();
|
||||
|
||||
private static final String EXTRA_DIRECTORY_NAME_KEY = "directory_name";
|
||||
private static final String EXTRA_IMPORTED_TRACK_COUNT = "imported_track_count";
|
||||
private static final String EXTRA_FILE_COUNT = "file_count";
|
||||
|
||||
private static final String FINISHED_DIALOG_TAG = "finished_dialog_tag";
|
||||
|
||||
private DismissCallback caller;
|
||||
|
||||
public static void showDialog(FragmentManager fragmentManager, String directoryDisplayName, int successCount, int fileCount) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(EXTRA_DIRECTORY_NAME_KEY, directoryDisplayName);
|
||||
bundle.putInt(EXTRA_IMPORTED_TRACK_COUNT, successCount);
|
||||
bundle.putInt(EXTRA_FILE_COUNT, fileCount);
|
||||
|
||||
ImportFinishedDialogFragment dialogFragment = new ImportFinishedDialogFragment();
|
||||
dialogFragment.setArguments(bundle);
|
||||
dialogFragment.show(fragmentManager, FINISHED_DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
caller = (DismissCallback) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
String directoryDisplayName = getArguments().getString(EXTRA_DIRECTORY_NAME_KEY);
|
||||
int importedTrackCount = getArguments().getInt(EXTRA_IMPORTED_TRACK_COUNT);
|
||||
int totalTrackCount = getArguments().getInt(EXTRA_FILE_COUNT);
|
||||
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalFiles = getResources().getQuantityString(R.plurals.files, totalTrackCount, totalTrackCount);
|
||||
if (importedTrackCount == totalTrackCount) {
|
||||
if (totalTrackCount == 0) {
|
||||
iconId = R.drawable.ic_dialog_info_24dp;
|
||||
titleId = R.string.import_no_file_title;
|
||||
message = getString(R.string.import_no_file, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_success_24dp;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(R.string.import_success, totalFiles, directoryDisplayName);
|
||||
}
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_error_24dp;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
|
||||
}
|
||||
Dialog dialog = new AlertDialog.Builder(getContext())
|
||||
.setCancelable(true).setIcon(iconId)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.generic_ok, (dialogInterface, which) -> {
|
||||
dialogInterface.dismiss();
|
||||
caller.onDismissed();
|
||||
})
|
||||
.setTitle(titleId)
|
||||
.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
caller = null;
|
||||
}
|
||||
|
||||
public interface DismissCallback {
|
||||
void onDismissed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.importer.GpxFileTrackImporter;
|
||||
import de.dennisguse.opentracks.io.file.importer.KmlFileTrackImporter;
|
||||
import de.dennisguse.opentracks.io.file.importer.KmzTrackImporter;
|
||||
import de.dennisguse.opentracks.io.file.importer.TrackImporter;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class ImportProgressDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String EXTRA_IS_TREE_KEY = "tree_uri";
|
||||
|
||||
private static final String EXTRA_DATA_URI_KEY = "data_uri";
|
||||
|
||||
private static final String TAG = ImportProgressDialogFragment.class.getSimpleName();
|
||||
|
||||
public static final String IMPORT_DIALOG_TAG = "import_dialog_tag";
|
||||
|
||||
private DismissCallback caller;
|
||||
|
||||
private ImportThread importThread;
|
||||
|
||||
private String directoryDisplayName;
|
||||
|
||||
private int trackImportSuccessCount;
|
||||
private int fileCount;
|
||||
|
||||
public static void showDialog(FragmentManager fragmentManager, Uri documentFileUri, boolean isDirectory) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_DATA_URI_KEY, documentFileUri);
|
||||
bundle.putBoolean(EXTRA_IS_TREE_KEY, isDirectory);
|
||||
|
||||
ImportProgressDialogFragment dialogFragment = new ImportProgressDialogFragment();
|
||||
dialogFragment.setArguments(bundle);
|
||||
dialogFragment.setRetainInstance(true);
|
||||
dialogFragment.show(fragmentManager, IMPORT_DIALOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// This fragment uses a background thread, so it should not be recreated during activity re-creation.
|
||||
setRetainInstance(true);
|
||||
|
||||
Uri directoryUri = getArguments().getParcelable(EXTRA_DATA_URI_KEY);
|
||||
DocumentFile documentFile;
|
||||
if (getArguments().getBoolean(EXTRA_IS_TREE_KEY)) {
|
||||
documentFile = DocumentFile.fromTreeUri(getContext(), directoryUri);
|
||||
} else {
|
||||
documentFile = DocumentFile.fromSingleUri(getContext(), directoryUri);
|
||||
}
|
||||
directoryDisplayName = FileUtils.getPath(documentFile);
|
||||
|
||||
importThread = new ImportThread(documentFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
caller = (DismissCallback) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context + " must implement " + DismissCallback.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progressDialog = DialogUtils.createHorizontalProgressDialog(
|
||||
getContext(), R.string.import_progress_message,
|
||||
dialogInterface -> {
|
||||
importThread.interrupt();
|
||||
dialogInterface.dismiss();
|
||||
caller.onImportCanceled(directoryDisplayName, trackImportSuccessCount, fileCount);
|
||||
}, directoryDisplayName);
|
||||
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(fileCount);
|
||||
progressDialog.setProgress(Math.min(trackImportSuccessCount, fileCount));
|
||||
|
||||
return progressDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setProgress();
|
||||
if (!importThread.isAlive()) {
|
||||
importThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!importThread.isAlive()) {
|
||||
importThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
caller = null;
|
||||
}
|
||||
|
||||
private void onProgress(int trackImportSuccessCount, int fileCount) {
|
||||
this.trackImportSuccessCount = trackImportSuccessCount;
|
||||
this.fileCount = fileCount;
|
||||
setProgress();
|
||||
}
|
||||
|
||||
private void setProgress() {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
if (dialog != null) {
|
||||
dialog.setIndeterminate(false);
|
||||
dialog.setMax(fileCount);
|
||||
dialog.setProgress(Math.min(trackImportSuccessCount, fileCount));
|
||||
}
|
||||
}
|
||||
|
||||
private void onImportCompleted(int successCount, int fileCount) {
|
||||
if (caller != null && !isDetached()) {
|
||||
caller.onImportFinished(directoryDisplayName, successCount, fileCount);
|
||||
}
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
private class ImportThread extends Thread {
|
||||
|
||||
private final DocumentFile file;
|
||||
|
||||
ImportThread(DocumentFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = ImportProgressDialogFragment.this.getContext();
|
||||
|
||||
List<DocumentFile> files = FileUtils.getFiles(file);
|
||||
|
||||
int importedTrackCount = 0;
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
onProgress(i, files.size());
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
return;
|
||||
}
|
||||
if (importFile(context, files.get(i))) {
|
||||
importedTrackCount++;
|
||||
}
|
||||
}
|
||||
|
||||
onImportCompleted(importedTrackCount, files.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a file.
|
||||
*
|
||||
* @param file the file
|
||||
*/
|
||||
private boolean importFile(final Context context, final DocumentFile file) {
|
||||
TrackImporter trackImporter;
|
||||
String fileExtension = FileUtils.getExtension(file);
|
||||
|
||||
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new GpxFileTrackImporter(context);
|
||||
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext(), -1L);
|
||||
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
|
||||
trackImporter = new KmzTrackImporter(context, -1L, file.getUri());
|
||||
} else {
|
||||
Log.i(TAG, "Unsupported file format.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
|
||||
return trackImporter.importFile(inputStream) != -1L;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface DismissCallback {
|
||||
void onImportCanceled(String directoryDisplayName, int successCount, int fileCount);
|
||||
|
||||
void onImportFinished(String directoryDisplayName, int successCount, int fileCount);
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
long markerId = getArguments().getLong(KEY_MARKER_ID);
|
||||
if (markerId == -1L) {
|
||||
Log.d(TAG, "invalid marker id");
|
||||
getFragmentManager().popBackStack();
|
||||
getParentFragmentManager().popBackStack();
|
||||
return;
|
||||
}
|
||||
contentProviderUtils = new ContentProviderUtils(getActivity());
|
||||
@@ -125,16 +125,13 @@ public class MarkerDetailFragment extends Fragment {
|
||||
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||
|
||||
photoView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handler.removeCallbacks(hideText);
|
||||
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
||||
textGradient.setVisibility(visibility);
|
||||
waypointInfo.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE) {
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
}
|
||||
photoView.setOnClickListener(v -> {
|
||||
handler.removeCallbacks(hideText);
|
||||
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
||||
textGradient.setVisibility(visibility);
|
||||
waypointInfo.setVisibility(visibility);
|
||||
if (visibility == View.VISIBLE) {
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
}
|
||||
});
|
||||
return view;
|
||||
@@ -222,7 +219,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
|
||||
if (waypoint == null) {
|
||||
Log.d(TAG, "waypoint is null");
|
||||
getFragmentManager().popBackStack();
|
||||
getParentFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2008 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.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatisticsRecordedFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private TrackStatistics trackStatistics = null;
|
||||
private String category = "";
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.statistics_recorded, container, false);
|
||||
}
|
||||
|
||||
/* Views */
|
||||
private TextView totalTimeValueView;
|
||||
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;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordedActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.unregisterTrackDataListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
trackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWaypoints() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypoint(Waypoint wpt) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewWaypointsDone() {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = trackStatistics == null ? Double.NaN : trackStatistics.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(false);
|
||||
if (trackIconValue != null) {
|
||||
TrackIconUtils.setIconSpinner(activitySpinner, trackIconValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Set time and start datetime
|
||||
if (trackStatistics != null) {
|
||||
movingTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()));
|
||||
totalTimeValueView.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = trackStatistics != null ? trackStatistics.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 = trackStatistics == null ? Double.NaN : trackStatistics.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 = trackStatistics != null ? trackStatistics.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+109
-132
@@ -1,19 +1,3 @@
|
||||
/*
|
||||
* Copyright 2008 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.content.SharedPreferences;
|
||||
@@ -31,15 +15,18 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
@@ -57,9 +44,9 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public class StatisticsRecordingFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
||||
private static final String TAG = StatisticsRecordingFragment.class.getSimpleName();
|
||||
|
||||
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND_MS;
|
||||
|
||||
@@ -81,12 +68,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -109,14 +93,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.stats, container, false);
|
||||
return inflater.inflate(R.layout.statistics_recording, container, false);
|
||||
}
|
||||
|
||||
/* Views */
|
||||
private View heartRateContainer;
|
||||
private View sensorHorizontalLine;
|
||||
private Group heartRateGroup;
|
||||
private TextView heartRateValueView;
|
||||
private TextView heartRateSensorView;
|
||||
private View cadenceContainer;
|
||||
private Group cadenceGroup;
|
||||
private TextView cadenceValueView;
|
||||
private TextView cadenceSensorView;
|
||||
|
||||
@@ -147,16 +132,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
private TextView speedMovingLabel;
|
||||
private TextView speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
private View elevationSeparator;
|
||||
private View elevationContainer;
|
||||
private View speedContainer;
|
||||
private Group elevationGroup;
|
||||
private TextView speedLabel;
|
||||
private TextView speedValue;
|
||||
private TextView speedUnit;
|
||||
private TextView elevationValue;
|
||||
private TextView elevationUnit;
|
||||
private View coordinateSeparator;
|
||||
private View coordinateContainer;
|
||||
private Group coordinateGroup;
|
||||
private TextView latitudeValue;
|
||||
private TextView longitudeValue;
|
||||
|
||||
@@ -164,11 +146,13 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
heartRateContainer = view.findViewById(R.id.stats_sensor_heart_rate_container);
|
||||
sensorHorizontalLine = view.findViewById(R.id.stats_sensor_horizontal_line);
|
||||
|
||||
heartRateGroup = view.findViewById(R.id.stats_sensor_heart_rate_group);
|
||||
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
|
||||
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||
|
||||
cadenceContainer = view.findViewById(R.id.stats_sensor_cadence_container);
|
||||
cadenceGroup = view.findViewById(R.id.stats_sensor_cadence_group);
|
||||
cadenceValueView = view.findViewById(R.id.stats_sensor_cadence_value);
|
||||
cadenceSensorView = view.findViewById(R.id.stats_sensor_cadence_sensor_value);
|
||||
|
||||
@@ -194,10 +178,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||
|
||||
elevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||
elevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||
elevationGroup = view.findViewById(R.id.stats_elevation_current_group);
|
||||
|
||||
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);
|
||||
@@ -205,8 +187,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
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);
|
||||
coordinateGroup = view.findViewById(R.id.stats_coordinate_group);
|
||||
|
||||
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
||||
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
||||
@@ -222,23 +203,17 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackDetailActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
((TrackRecordingActivity) getActivity()).chooseActivityType(category);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -275,10 +250,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
heartRateContainer = null;
|
||||
sensorHorizontalLine = null;
|
||||
|
||||
heartRateGroup = null;
|
||||
heartRateValueView = null;
|
||||
heartRateSensorView = null;
|
||||
cadenceContainer = null;
|
||||
cadenceGroup = null;
|
||||
cadenceValueView = null;
|
||||
cadenceSensorView = null;
|
||||
|
||||
@@ -304,10 +281,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
elevationSeparator = null;
|
||||
elevationContainer = null;
|
||||
elevationGroup = null;
|
||||
|
||||
speedContainer = null;
|
||||
speedLabel = null;
|
||||
speedValue = null;
|
||||
speedUnit = null;
|
||||
@@ -315,8 +290,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
elevationValue = null;
|
||||
elevationUnit = null;
|
||||
|
||||
coordinateSeparator = null;
|
||||
coordinateContainer = null;
|
||||
coordinateGroup = null;
|
||||
|
||||
latitudeValue = null;
|
||||
longitudeValue = null;
|
||||
@@ -325,14 +299,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -356,24 +327,21 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
if (lastTrackPoint != null) {
|
||||
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
||||
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!hasFix || !hasGoodFix) {
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
if (lastTrackPoint != null) {
|
||||
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
||||
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!hasFix || !hasGoodFix) {
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
}
|
||||
setLocationValues();
|
||||
}
|
||||
setLocationValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -399,7 +367,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub = ((TrackRecordingActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||
}
|
||||
|
||||
@@ -442,65 +410,79 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
SensorDataSet sensorDataSet = null;
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
||||
Log.d(TAG, "Cannot get the track recording service.");
|
||||
} else {
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
}
|
||||
|
||||
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
setCadenceSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
setHeartRateSensorData(sensorDataSet);
|
||||
setCadenceSensorData(sensorDataSet);
|
||||
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
}
|
||||
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
||||
isVisible = View.INVISIBLE;
|
||||
if (PreferencesUtils.isBluetoothHeartRateSensorAddressNone(getContext())) {
|
||||
isVisible = View.GONE;
|
||||
}
|
||||
heartRateContainer.setVisibility(isVisible);
|
||||
heartRateGroup.setVisibility(isVisible);
|
||||
setVisibilitySensorHorizontalLine();
|
||||
|
||||
if (isRecording) {
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
sensorName = sensorDataSet.getHeartRate().getSensorName();
|
||||
if (sensorDataSet.getHeartRate().hasHeartRate_bpm() && sensorDataSet.getHeartRate().isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getHeartRate().getHeartRate_bpm(), 0);
|
||||
}
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
SensorDataHeartRate data = sensorDataSet.getHeartRate();
|
||||
|
||||
sensorName = data.getSensorName();
|
||||
if (data.hasHeartRate_bpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getHeartRate_bpm(), 0);
|
||||
}
|
||||
|
||||
heartRateSensorView.setText(sensorName);
|
||||
heartRateValueView.setText(sensorValue);
|
||||
}
|
||||
|
||||
heartRateSensorView.setText(sensorName);
|
||||
heartRateValueView.setText(sensorValue);
|
||||
}
|
||||
|
||||
private void setCadenceSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
private void setCadenceSensorData(SensorDataSet sensorDataSet) {
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
||||
isVisible = View.INVISIBLE;
|
||||
if (PreferencesUtils.isBluetoothCyclingCadenceSensorAddressNone(getContext())) {
|
||||
isVisible = View.GONE;
|
||||
}
|
||||
cadenceContainer.setVisibility(isVisible);
|
||||
cadenceGroup.setVisibility(isVisible);
|
||||
setVisibilitySensorHorizontalLine();
|
||||
|
||||
if (isRecording) {
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
||||
sensorName = sensorDataSet.getCyclingCadence().getSensorName();
|
||||
if (sensorDataSet.getCyclingCadence().hasCadence_rpm() && sensorDataSet.getCyclingCadence().isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(sensorDataSet.getCyclingCadence().getCadence_rpm(), 0);
|
||||
}
|
||||
String sensorValue = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null) {
|
||||
SensorDataCycling.Cadence data = sensorDataSet.getCyclingCadence();
|
||||
sensorName = data.getSensorName();
|
||||
|
||||
if (data.hasCadence_rpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getCadence_rpm(), 0);
|
||||
}
|
||||
|
||||
cadenceSensorView.setText(sensorName);
|
||||
cadenceValueView.setText(sensorValue);
|
||||
}
|
||||
|
||||
cadenceSensorView.setText(sensorName);
|
||||
cadenceValueView.setText(sensorValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* If cadence and hear rate groups are invisible then sensor horizontal line hast to be invisible too.
|
||||
*/
|
||||
private void setVisibilitySensorHorizontalLine() {
|
||||
if (cadenceGroup.getVisibility() != View.VISIBLE && heartRateGroup.getVisibility() != View.VISIBLE) {
|
||||
sensorHorizontalLine.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
if (isRecording) {
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) {
|
||||
if (sensorDataSet.getCyclingSpeed().hasSpeed_mps() && sensorDataSet.getCyclingSpeed().isRecent()) {
|
||||
setSpeed(sensorDataSet.getCyclingSpeed().getSpeed_mps());
|
||||
SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed();
|
||||
|
||||
if (data.hasSpeed_mps() && data.isRecent()) {
|
||||
setSpeed(data.getSpeed_mps());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -574,7 +556,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
// Make elevation visible?
|
||||
{
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,18 +585,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
private void setLocationValues() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean isRecording = isSelectedTrackRecording();
|
||||
|
||||
// Set speed/pace
|
||||
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
setSpeed(speed);
|
||||
}
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
setSpeed(speed);
|
||||
|
||||
// Set elevation
|
||||
boolean showElevation = isRecording && PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
||||
@@ -625,10 +603,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
boolean showCoordinate = PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateGroup.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
|
||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||
@@ -12,7 +12,6 @@ import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmlTrackWriter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporterListener;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackWriter;
|
||||
|
||||
/**
|
||||
@@ -74,8 +73,8 @@ public enum TrackFileFormat {
|
||||
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,8 +100,8 @@ public enum TrackFileFormat {
|
||||
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,8 +133,8 @@ public enum TrackFileFormat {
|
||||
return MIME_KMZ;
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
@@ -163,8 +162,8 @@ public enum TrackFileFormat {
|
||||
return MIME_KMZ;
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
@@ -197,10 +196,10 @@ public enum TrackFileFormat {
|
||||
|
||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
||||
|
||||
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) {
|
||||
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, boolean exportPhotos) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
|
||||
|
||||
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
|
||||
}
|
||||
@@ -210,10 +209,10 @@ public enum TrackFileFormat {
|
||||
*/
|
||||
public abstract String getMimeType();
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
|
||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,174 +16,58 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ExportFinishedDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ExportProgressDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
/**
|
||||
* An activity for saving tracks to the external storage.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ExportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
|
||||
public class ExportActivity extends FragmentActivity implements ExportProgressDialogFragment.DismissCallback, ExportFinishedDialogFragment.DismissCallback {
|
||||
|
||||
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private static final int DIALOG_RESULT_ID = 1;
|
||||
public static final String EXTRA_TRACKFILEFORMAT_KEY = "trackfileformat";
|
||||
|
||||
private String directoryDisplayName;
|
||||
|
||||
private ExportAsyncTask exportAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
private int processedTrackCount;
|
||||
private int totalTrackCount;
|
||||
|
||||
private Uri directoryUri;
|
||||
private static final String BUNDLE_ACTIVITY_RECREATED = "activity_recreated";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
directoryUri = resultData.getData();
|
||||
fileTypeDialogStart();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
if (savedInstanceState == null || !savedInstanceState.getBoolean(BUNDLE_ACTIVITY_RECREATED, false)) {
|
||||
Uri directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
TrackFileFormat trackfileformat = (TrackFileFormat) getIntent().getSerializableExtra(EXTRA_TRACKFILEFORMAT_KEY);
|
||||
ExportProgressDialogFragment.showDialog(getSupportFragmentManager(), trackfileformat, directoryUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void fileTypeDialogStart() {
|
||||
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.export_all_title, R.string.export_all_option);
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(BUNDLE_ACTIVITY_RECREATED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
||||
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
DocumentFile exportDirectory = pickedDirectory.findFile(FileUtils.EXPORT_DIR);
|
||||
if (exportDirectory == null) {
|
||||
exportDirectory = pickedDirectory.createDirectory(FileUtils.EXPORT_DIR);
|
||||
}
|
||||
public void onExportCanceled(String directoryDisplayName, int successCount, int fileCount) {
|
||||
onExportFinished(directoryDisplayName, successCount, fileCount);
|
||||
}
|
||||
|
||||
directoryDisplayName = FileUtils.getPath(exportDirectory);
|
||||
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, exportDirectory);
|
||||
exportAsyncTask.execute();
|
||||
@Override
|
||||
public void onExportFinished(final String directoryDisplayName, final int successCount, final int fileCount) {
|
||||
runOnUiThread(() -> ExportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissed() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
||||
R.string.export_progress_message,
|
||||
new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
exportAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalTracks = getResources()
|
||||
.getQuantityString(R.plurals.tracks, totalTrackCount, totalTrackCount);
|
||||
if (processedTrackCount == totalTrackCount) {
|
||||
iconId = R.drawable.ic_dialog_success_24dp;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(
|
||||
R.string.export_success, totalTracks, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_error_24dp;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(R.string.export_error, processedTrackCount, totalTracks, directoryDisplayName);
|
||||
}
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true)
|
||||
.setIcon(iconId).setMessage(message)
|
||||
.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int arg1) {
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}).setTitle(titleId);
|
||||
return builder.create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param aSuccessCount the number of tracks successfully saved
|
||||
* @param aTotalCount the number of tracks to save
|
||||
*/
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
|
||||
processedTrackCount = aSuccessCount;
|
||||
totalTrackCount = aTotalCount;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
showDialog(DIALOG_RESULT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(DIALOG_PROGRESS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*
|
||||
* @param number the number of points saved
|
||||
* @param max the maximum number of points
|
||||
*/
|
||||
public void setProgressDialogValue(int number, int max) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(max);
|
||||
progressDialog.setProgress(Math.min(number, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,184 +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.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
/**
|
||||
* Async Task to save tracks to the external storage.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Make independent from ExportActivity?
|
||||
class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final DocumentFile directory;
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private ExportActivity exportActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private boolean completed = false;
|
||||
|
||||
private int processedTrackCount = 0;
|
||||
private int totalTrackCount = 0;
|
||||
|
||||
/**
|
||||
* Creates an AsyncTask.
|
||||
*
|
||||
* @param exportActivity the activity currently associated with this task
|
||||
* @param trackFileFormat the track file format
|
||||
* @param directory the directory to write the file
|
||||
*/
|
||||
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
this.exportActivity = exportActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.directory = directory;
|
||||
context = exportActivity.getApplicationContext();
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (exportActivity != null) {
|
||||
exportActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
|
||||
wakeLock = SystemUtils.acquireWakeLock(exportActivity, wakeLock);
|
||||
|
||||
return exportAllTracks();
|
||||
} finally {
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (exportActivity != null) {
|
||||
exportActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
completed = true;
|
||||
if (exportActivity != null) {
|
||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
completed = true;
|
||||
if (exportActivity != null) {
|
||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the track to one file.
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
private Boolean exportTrack(Track track) {
|
||||
TrackExporterListener trackExporterListener = new TrackExporterListener() {
|
||||
@Override
|
||||
public void onProgressUpdate(int number, int max) {
|
||||
//Update the progress dialog once every 500 points.
|
||||
if (number % 500 == 0) {
|
||||
publishProgress(number, max);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, trackExporterListener);
|
||||
|
||||
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
||||
|
||||
// Overwrite a file if it exists; DocumentFile.createFile() creates a new file appending a suffix if the displayname already exists.
|
||||
DocumentFile file = directory.findFile(fileName);
|
||||
if (file == null) {
|
||||
file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
|
||||
if (trackExporter.writeTrack(context, outputStream)) {
|
||||
return true;
|
||||
} else {
|
||||
if (!file.delete()) {
|
||||
Log.d(TAG, "Unable to delete file");
|
||||
}
|
||||
Log.e(TAG, "Unable to export track");
|
||||
return false;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Unable to open file " + file.getName(), e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close file output stream", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all the tracks.
|
||||
*/
|
||||
private Boolean exportAllTracks() {
|
||||
try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
if (cursor == null) {
|
||||
return false;
|
||||
}
|
||||
totalTrackCount = cursor.getCount();
|
||||
for (int i = 0; i < totalTrackCount; i++) {
|
||||
if (isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
cursor.moveToPosition(i);
|
||||
Track track = contentProviderUtils.createTrack(cursor);
|
||||
if (track != null && exportTrack(track)) {
|
||||
processedTrackCount++;
|
||||
}
|
||||
publishProgress(i + 1, totalTrackCount);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ public class FileTrackExporter implements TrackExporter {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Track[] tracks;
|
||||
private final TrackWriter trackWriter;
|
||||
private final TrackExporterListener trackExporterListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -52,13 +51,11 @@ public class FileTrackExporter implements TrackExporter {
|
||||
* @param contentProviderUtils the content provider utils
|
||||
* @param trackWriter the track writer
|
||||
* @param tracks the tracks
|
||||
* @param trackExporterListener the track export listener
|
||||
*/
|
||||
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.tracks = tracks;
|
||||
this.trackWriter = trackWriter;
|
||||
this.trackExporterListener = trackExporterListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,9 +155,6 @@ public class FileTrackExporter implements TrackExporter {
|
||||
|
||||
// Write the current trackPoint
|
||||
trackWriter.writeTrackPoint(trackPoint);
|
||||
if (trackExporterListener != null) {
|
||||
trackExporterListener.onProgressUpdate(locationNumber, trackPointIterator.getCount());
|
||||
}
|
||||
} else {
|
||||
if (wroteSegment) {
|
||||
trackWriter.writeCloseSegment();
|
||||
|
||||
@@ -33,11 +33,13 @@ import de.dennisguse.opentracks.util.StringUtils;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
//TODO Can we export SensorData in GPX?
|
||||
public class GpxTrackWriter implements TrackWriter {
|
||||
|
||||
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
|
||||
static {
|
||||
/*
|
||||
@@ -50,6 +52,15 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
COORDINATE_FORMAT.setMaximumFractionDigits(6);
|
||||
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
|
||||
COORDINATE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
SPEED_FORMAT.setMaximumFractionDigits(2);
|
||||
SPEED_FORMAT.setGroupingUsed(false);
|
||||
|
||||
HEARTRATE_FORMAT.setMaximumFractionDigits(0);
|
||||
HEARTRATE_FORMAT.setGroupingUsed(false);
|
||||
|
||||
CADENCE_FORMAT.setMaximumFractionDigits(0);
|
||||
CADENCE_FORMAT.setGroupingUsed(false);
|
||||
}
|
||||
|
||||
private final String creator;
|
||||
@@ -82,11 +93,12 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
|
||||
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1"
|
||||
+ " http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd\">");
|
||||
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
|
||||
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
|
||||
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">");
|
||||
printWriter.println("<metadata>");
|
||||
|
||||
Track track = tracks[0];
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
|
||||
@@ -174,8 +186,28 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
if (trackPoint.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
|
||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
|
||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence()) {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
printWriter.println("<gpxtpx:cad>" + HEARTRATE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
||||
}
|
||||
|
||||
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
|
||||
}
|
||||
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,10 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
private static final String TRACK_STYLE = "track";
|
||||
private static final String SCHEMA_ID = "schema";
|
||||
|
||||
public static final String SENSOR_TYPE_CADENCE = "cadence";
|
||||
public static final String SENSOR_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String SENSOR_TYPE_POWER = "power";
|
||||
public static final String EXTENDED_DATA_TYPE_SPEED = "speed";
|
||||
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||
|
||||
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
||||
@@ -64,6 +65,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private PrintWriter printWriter;
|
||||
private final List<Float> speedList = new ArrayList<>();
|
||||
private final List<Float> powerList = new ArrayList<>();
|
||||
private final List<Float> cadenceList = new ArrayList<>();
|
||||
private final List<Float> heartRateList = new ArrayList<>();
|
||||
@@ -123,10 +125,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
writePlacemarkerStyle(WAYPOINT_STYLE, WAYPOINT_ICON, 20, 2);
|
||||
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
|
||||
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_SPEED, context.getString(R.string.description_speed_ms));
|
||||
|
||||
if (exportSensorData) {
|
||||
writeSensorStyle(SENSOR_TYPE_POWER, context.getString(R.string.description_sensor_power));
|
||||
writeSensorStyle(SENSOR_TYPE_CADENCE, context.getString(R.string.description_sensor_cadence));
|
||||
writeSensorStyle(SENSOR_TYPE_HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_POWER, context.getString(R.string.description_sensor_power));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_CADENCE, context.getString(R.string.description_sensor_cadence));
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_HEART_RATE, context.getString(R.string.description_sensor_heart_rate));
|
||||
}
|
||||
printWriter.println("</Schema>");
|
||||
}
|
||||
@@ -229,6 +233,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
public void writeOpenSegment() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<gx:Track>");
|
||||
speedList.clear();
|
||||
powerList.clear();
|
||||
cadenceList.clear();
|
||||
heartRateList.clear();
|
||||
@@ -240,15 +245,18 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<ExtendedData>");
|
||||
printWriter.println("<SchemaData schemaUrl=\"#" + SCHEMA_ID + "\">");
|
||||
if (speedList.size() > 0) {
|
||||
writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED);
|
||||
}
|
||||
if (exportSensorData) {
|
||||
if (powerList.size() > 0) {
|
||||
writeSensorData(powerList, SENSOR_TYPE_POWER);
|
||||
writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER);
|
||||
}
|
||||
if (cadenceList.size() > 0) {
|
||||
writeSensorData(cadenceList, SENSOR_TYPE_CADENCE);
|
||||
writeSimpleArrayData(cadenceList, EXTENDED_DATA_TYPE_CADENCE);
|
||||
}
|
||||
if (heartRateList.size() > 0) {
|
||||
writeSensorData(heartRateList, SENSOR_TYPE_HEART_RATE);
|
||||
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
|
||||
}
|
||||
}
|
||||
printWriter.println("</SchemaData>");
|
||||
@@ -266,6 +274,10 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
|
||||
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
speedList.add(trackPoint.getSpeed());
|
||||
}
|
||||
|
||||
if (exportSensorData) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRateList.add(trackPoint.getHeartRate_bpm());
|
||||
@@ -281,12 +293,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the sensor data.
|
||||
* Writes the simple array data.
|
||||
*
|
||||
* @param list a list of sensor data
|
||||
* @param name the name of the sensor data
|
||||
* @param list a list of simple array data
|
||||
* @param name the name of the simple array data
|
||||
*/
|
||||
private void writeSensorData(List<Float> list, String name) {
|
||||
private void writeSimpleArrayData(List<Float> list, String name) {
|
||||
printWriter.println("<gx:SimpleArrayData name=\"" + name + "\">");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
|
||||
@@ -446,14 +458,14 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a sensor style.
|
||||
* Writes a simple array style.
|
||||
*
|
||||
* @param name the name of the sesnor
|
||||
* @param sensorType the sensor display name
|
||||
* @param name the name of the simple array.
|
||||
* @param extendedDataType the extended data display name
|
||||
*/
|
||||
private void writeSensorStyle(String name, String sensorType) {
|
||||
private void writeSimpleArrayStyle(String name, String extendedDataType) {
|
||||
printWriter.println("<gx:SimpleArrayField name=\"" + name + "\" type=\"float\">");
|
||||
printWriter.println("<displayName>" + StringUtils.formatCData(sensorType) + "</displayName>");
|
||||
printWriter.println("<displayName>" + StringUtils.formatCData(extendedDataType) + "</displayName>");
|
||||
printWriter.println("</gx:SimpleArrayField>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +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.io.file.exporter;
|
||||
|
||||
/**
|
||||
* Listener for {@link TrackExporter} progress.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface TrackExporterListener {
|
||||
|
||||
/**
|
||||
* Called to update progress.
|
||||
*
|
||||
* @param number the number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s written
|
||||
* @param max the maximum number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s in a {@link de.dennisguse.opentracks.content.data.Track}, for calculation of
|
||||
* completion percentage
|
||||
*/
|
||||
void onProgressUpdate(int number, int max);
|
||||
}
|
||||
+71
-31
@@ -31,6 +31,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
@@ -72,6 +73,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
// The current element content
|
||||
//TODO Should be made private and getter be used by child classes.
|
||||
protected String content;
|
||||
|
||||
protected String icon;
|
||||
protected String name;
|
||||
protected String description;
|
||||
@@ -80,8 +82,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
protected String longitude;
|
||||
protected String altitude;
|
||||
protected String time;
|
||||
protected String speed;
|
||||
protected String heartrate;
|
||||
protected String cadence;
|
||||
protected String power;
|
||||
protected String waypointType;
|
||||
protected String photoUrl;
|
||||
protected String uuid;
|
||||
|
||||
// The current track data
|
||||
private TrackData trackData;
|
||||
@@ -90,11 +97,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
private Locator locator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId the track id to import to. -1L to import to a new track.
|
||||
*/
|
||||
@Deprecated
|
||||
// Remove importTrackId
|
||||
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
@@ -243,6 +250,14 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (name != null) {
|
||||
trackData.track.setName(name);
|
||||
}
|
||||
|
||||
try {
|
||||
trackData.track.setUuid(UUID.fromString(uuid));
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
Log.w(TAG, "could not parse Track UUID, generating a new one.");
|
||||
trackData.track.setUuid(UUID.randomUUID());
|
||||
}
|
||||
|
||||
if (description != null) {
|
||||
trackData.track.setDescription(description);
|
||||
}
|
||||
@@ -319,20 +334,22 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
// Calculate derived attributes from the previous point
|
||||
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
||||
long timeDifference = trackPoint.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
if (!trackPoint.hasSpeed()) {
|
||||
long timeDifference = trackPoint.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(TAG, "Time difference not postive.");
|
||||
} else {
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
Log.w(TAG, "Time difference not positive.");
|
||||
} else {
|
||||
|
||||
/*
|
||||
* We don't have a speed and bearing in GPX, make something up from the last two points.
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
|
||||
trackPoint.setSpeed((float) speed);
|
||||
/*
|
||||
* We don't have a speed and bearing in GPX, make something up from the last two points.
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
|
||||
trackPoint.setSpeed((float) speed);
|
||||
}
|
||||
}
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
}
|
||||
@@ -389,9 +406,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
Uri photoUri = FileUtils.getUriForFile(context, file);
|
||||
return "" + photoUri;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,34 +417,58 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (latitude == null || longitude == null) {
|
||||
return null;
|
||||
}
|
||||
double latitudeValue;
|
||||
double longitudeValue;
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
|
||||
try {
|
||||
latitudeValue = Double.parseDouble(latitude);
|
||||
longitudeValue = Double.parseDouble(longitude);
|
||||
trackPoint.setLatitude(Double.parseDouble(latitude));
|
||||
trackPoint.setLongitude(Double.parseDouble(longitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
|
||||
}
|
||||
Double altitudeValue = null;
|
||||
|
||||
if (time == null) {
|
||||
trackPoint.setTime(trackData.importTime);
|
||||
} else {
|
||||
try {
|
||||
trackPoint.setTime(StringUtils.parseTime(time));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (altitude != null) {
|
||||
try {
|
||||
altitudeValue = Double.parseDouble(altitude);
|
||||
trackPoint.setAltitude(Double.parseDouble(altitude));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)), e);
|
||||
}
|
||||
}
|
||||
|
||||
long timeValue;
|
||||
if (time == null) {
|
||||
timeValue = trackData.importTime;
|
||||
} else {
|
||||
if (speed != null) {
|
||||
try {
|
||||
timeValue = StringUtils.parseTime(time);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
trackPoint.setSpeed(Float.parseFloat(speed));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
}
|
||||
}
|
||||
return new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
if (heartrate != null) {
|
||||
try {
|
||||
trackPoint.setHeartRate_bpm(Float.parseFloat(heartrate));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse heart rate: %s", heartrate)), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (cadence != null) {
|
||||
try {
|
||||
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
||||
} catch (Exception e) {
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||
}
|
||||
}
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
|
||||
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri directoryUri = resultData.getData();
|
||||
|
||||
startActivity(createIntent(directoryUri));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Intent createIntent(Uri directoryUri);
|
||||
|
||||
|
||||
public static class ImportDirectoryChooserActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
protected Intent createIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ImportActivity.class);
|
||||
intent.putExtra(ImportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExportDirectoryChooserActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
protected Intent createIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(this));
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
* Imports a GPX file.
|
||||
* Uses https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
@@ -48,6 +49,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
|
||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -125,6 +130,20 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
waypointType = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_SPEED:
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
case TAG_EXTENSION_HEARTRATE:
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_CADENCE:
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
@@ -149,6 +168,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||
altitude = null;
|
||||
time = null;
|
||||
speed = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,175 +16,59 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.DialogUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.fragments.ImportFinishedDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ImportProgressDialogFragment;
|
||||
|
||||
/**
|
||||
* An activity to import files from the external storage.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ImportActivity extends FragmentActivity implements FileTypeDialogFragment.FileTypeCaller {
|
||||
public class ImportActivity extends FragmentActivity implements ImportProgressDialogFragment.DismissCallback, ImportFinishedDialogFragment.DismissCallback {
|
||||
|
||||
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||
private static final String TAG = ImportActivity.class.getSimpleName();
|
||||
|
||||
private static final int DIALOG_PROGRESS_ID = 0;
|
||||
private static final int DIALOG_RESULT_ID = 1;
|
||||
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
|
||||
private String directoryDisplayName;
|
||||
|
||||
private ImportAsyncTask importAsyncTask;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
private int importedTrackCount;
|
||||
private int totalTrackCount;
|
||||
|
||||
private Uri directoryUri;
|
||||
private static final String BUNDLE_ACTIVITY_RECREATED = "activity_recreated";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
directoryUri = resultData.getData();
|
||||
fileTypeDialogStart();
|
||||
if (savedInstanceState == null || !savedInstanceState.getBoolean(BUNDLE_ACTIVITY_RECREATED, false)) {
|
||||
if (getIntent().getData() != null) {
|
||||
ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getData(), false);
|
||||
} else {
|
||||
finish();
|
||||
// Started from DirectoryChooserActivity
|
||||
ImportProgressDialogFragment.showDialog(getSupportFragmentManager(), getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fileTypeDialogStart() {
|
||||
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.import_selection_title, R.string.import_selection_option);
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(BUNDLE_ACTIVITY_RECREATED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
||||
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
public void onImportCanceled(String directoryDisplayName, int successCount, int fileCount) {
|
||||
onImportFinished(directoryDisplayName, successCount, fileCount);
|
||||
}
|
||||
|
||||
directoryDisplayName = FileUtils.getPath(pickedDirectory);
|
||||
|
||||
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
|
||||
importAsyncTask.execute();
|
||||
@Override
|
||||
public void onImportFinished(final String directoryDisplayName, final int successCount, final int fileCount) {
|
||||
runOnUiThread(() -> ImportFinishedDialogFragment.showDialog(getSupportFragmentManager(), directoryDisplayName, successCount, fileCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissed() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
switch (id) {
|
||||
case DIALOG_PROGRESS_ID:
|
||||
progressDialog = DialogUtils.createHorizontalProgressDialog(
|
||||
this, R.string.import_progress_message, new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
importAsyncTask.cancel(true);
|
||||
dialog.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}, directoryDisplayName);
|
||||
return progressDialog;
|
||||
case DIALOG_RESULT_ID:
|
||||
int iconId;
|
||||
int titleId;
|
||||
String message;
|
||||
String totalFiles = getResources()
|
||||
.getQuantityString(R.plurals.files, totalTrackCount, totalTrackCount);
|
||||
if (importedTrackCount == totalTrackCount) {
|
||||
if (totalTrackCount == 0) {
|
||||
iconId = R.drawable.ic_dialog_info_24dp;
|
||||
titleId = R.string.import_no_file_title;
|
||||
message = getString(R.string.import_no_file, directoryDisplayName);
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_success_24dp;
|
||||
titleId = R.string.generic_success_title;
|
||||
message = getString(R.string.import_success, totalFiles, directoryDisplayName);
|
||||
}
|
||||
} else {
|
||||
iconId = R.drawable.ic_dialog_error_24dp;
|
||||
titleId = R.string.generic_error_title;
|
||||
message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
|
||||
}
|
||||
return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
|
||||
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialogInterface) {
|
||||
dialogInterface.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int which) {
|
||||
dialogInterface.dismiss();
|
||||
onDismissed();
|
||||
}
|
||||
}).setTitle(titleId).create();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes when the associated AsyncTask completes.
|
||||
*
|
||||
* @param aSuccessCount the number of files successfully imported
|
||||
* @param aTotalCount the number of files to import
|
||||
*/
|
||||
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
|
||||
importedTrackCount = aSuccessCount;
|
||||
totalTrackCount = aTotalCount;
|
||||
removeDialog(DIALOG_PROGRESS_ID);
|
||||
showDialog(DIALOG_RESULT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress dialog.
|
||||
*/
|
||||
public void showProgressDialog() {
|
||||
showDialog(DIALOG_PROGRESS_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress dialog value.
|
||||
*
|
||||
* @param number the number of files imported
|
||||
* @param max the maximum number of files
|
||||
*/
|
||||
public void setProgressDialogValue(int number, int max) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setMax(max);
|
||||
progressDialog.setProgress(Math.min(number, max));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +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.io.file.importer;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
|
||||
/**
|
||||
* AsyncTask to import files from the external storage.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
private final DocumentFile directory;
|
||||
|
||||
// TODO Use weak reference
|
||||
private ImportActivity importActivity;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private int importTrackCount;
|
||||
private int totalTrackCount;
|
||||
private long lastSuccessfulTrackId;
|
||||
|
||||
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
this.importActivity = importActivity;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.directory = directory;
|
||||
|
||||
importTrackCount = 0;
|
||||
totalTrackCount = 0;
|
||||
lastSuccessfulTrackId = -1L;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (importActivity != null) {
|
||||
importActivity.showProgressDialog();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of files.
|
||||
*/
|
||||
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
|
||||
List<DocumentFile> files = new ArrayList<>();
|
||||
|
||||
for (DocumentFile candidate : file.listFiles()) {
|
||||
if (!candidate.isDirectory()) {
|
||||
String extension = FileUtils.getExtension(candidate.getName());
|
||||
if (extension != null && trackFileFormat.getExtension().equals(extension)) {
|
||||
files.add(candidate);
|
||||
}
|
||||
} else {
|
||||
files.addAll(getFiles(candidate, trackFileFormat));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... values) {
|
||||
if (importActivity != null) {
|
||||
importActivity.setProgressDialogValue(values[0], values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
if (importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||
importActivity = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
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
|
||||
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
||||
if (!isRecording || isPaused) {
|
||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
||||
}
|
||||
|
||||
List<DocumentFile> files = getFiles(directory, trackFileFormat);
|
||||
totalTrackCount = files.size();
|
||||
|
||||
for (int i = 0; i < totalTrackCount; i++) {
|
||||
if (isCancelled()) {
|
||||
// If cancelled, return true to show the number of files imported
|
||||
return true;
|
||||
}
|
||||
if (importFile(files.get(i))) {
|
||||
importTrackCount++;
|
||||
}
|
||||
publishProgress(i + 1, totalTrackCount);
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a file.
|
||||
*
|
||||
* @param file the file
|
||||
*/
|
||||
private boolean importFile(final DocumentFile file) {
|
||||
TrackImporter trackImporter;
|
||||
if (trackFileFormat == TrackFileFormat.GPX) {
|
||||
trackImporter = new GpxFileTrackImporter(importActivity);
|
||||
} else {
|
||||
//KML or KMZ
|
||||
String extension = FileUtils.getExtension(file.getName());
|
||||
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
||||
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
|
||||
} else {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(importActivity);
|
||||
Uri uri = contentProviderUtils.insertTrack(new Track());
|
||||
long newId = Long.parseLong(uri.getLastPathSegment());
|
||||
|
||||
trackImporter = new KmzTrackImporter(importActivity, newId, file.getUri());
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream inputStream = importActivity.getContentResolver().openInputStream(file.getUri())) {
|
||||
lastSuccessfulTrackId = trackImporter.importFile(inputStream);
|
||||
return lastSuccessfulTrackId != -1L;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,18 +61,18 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
private static final String ATTRIBUTE_NAME = "name";
|
||||
|
||||
private boolean trackStarted = false;
|
||||
private String sensorType;
|
||||
private String extendedDataType;
|
||||
private ArrayList<TrackPoint> trackPoints;
|
||||
private ArrayList<Float> speedList;
|
||||
private ArrayList<Float> cadenceList;
|
||||
private ArrayList<Float> heartRateList;
|
||||
private ArrayList<Float> powerList;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId track id to import to. -1L to import to a new track.
|
||||
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
|
||||
*/
|
||||
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
|
||||
public KmlFileTrackImporter(Context context, long importTrackId) {
|
||||
this(context, importTrackId, new ContentProviderUtils(context));
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
onTrackSegmentStart();
|
||||
break;
|
||||
case TAG_GX_SIMPLE_ARRAY_DATA:
|
||||
onSensorDataStart(attributes);
|
||||
onExtendedDataStart(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
} else if (tag.equals(TAG_GX_COORD)) {
|
||||
onTrackPointEnd();
|
||||
} else if (tag.equals(TAG_GX_VALUE)) {
|
||||
onSensorValueEnd();
|
||||
onExtendedDataValueEnd();
|
||||
} else if (tag.equals(TAG_NAME)) {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
@@ -209,6 +209,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
protected void onTrackSegmentStart() {
|
||||
super.onTrackSegmentStart();
|
||||
trackPoints = new ArrayList<>();
|
||||
speedList = new ArrayList<>();
|
||||
heartRateList = new ArrayList<>();
|
||||
cadenceList = new ArrayList<>();
|
||||
powerList = new ArrayList<>();
|
||||
@@ -222,6 +223,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
if (i < speedList.size()) {
|
||||
trackPoint.setSpeed(speedList.get(i));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
}
|
||||
@@ -229,7 +233,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
trackPoint.setCyclingCadence_rpm(cadenceList.get(i));
|
||||
}
|
||||
if (i < powerList.size()) {
|
||||
trackPoint.setPower(cadenceList.get(i));
|
||||
trackPoint.setPower(powerList.get(i));
|
||||
}
|
||||
|
||||
insertTrackPoint(trackPoint);
|
||||
@@ -261,16 +265,16 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor data start. gx:SimpleArrayData start tag.
|
||||
* On extended data start. gx:SimpleArrayData start tag.
|
||||
*/
|
||||
private void onSensorDataStart(Attributes attributes) {
|
||||
sensorType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
private void onExtendedDataStart(Attributes attributes) {
|
||||
extendedDataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* On sensor value end. gx:value end tag.
|
||||
* On extended data value end. gx:value end tag.
|
||||
*/
|
||||
private void onSensorValueEnd() throws SAXException {
|
||||
private void onExtendedDataValueEnd() throws SAXException {
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
@@ -284,18 +288,21 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
|
||||
}
|
||||
switch (sensorType) {
|
||||
case KmlTrackWriter.SENSOR_TYPE_POWER:
|
||||
switch (extendedDataType) {
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_SPEED:
|
||||
speedList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_POWER:
|
||||
powerList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.SENSOR_TYPE_HEART_RATE:
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_HEART_RATE:
|
||||
heartRateList.add(value);
|
||||
break;
|
||||
case KmlTrackWriter.SENSOR_TYPE_CADENCE:
|
||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_CADENCE:
|
||||
cadenceList.add(value);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Data from sensor " + sensorType + " is not (yet) supported.");
|
||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,16 +53,15 @@ public class KmzTrackImporter implements TrackImporter {
|
||||
|
||||
private final Context context;
|
||||
private final long importTrackId;
|
||||
private Uri uriKmzFile;
|
||||
private final Uri uriKmzFile;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
|
||||
* @param uriFile URI of the kmz file.
|
||||
*/
|
||||
KmzTrackImporter(Context context, long importTrackId, Uri uriFile) {
|
||||
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
|
||||
public KmzTrackImporter(Context context, long importTrackId, Uri uriFile) {
|
||||
this.context = context;
|
||||
this.importTrackId = importTrackId;
|
||||
this.uriKmzFile = uriFile;
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.io.InputStream;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
interface TrackImporter {
|
||||
public interface TrackImporter {
|
||||
|
||||
/**
|
||||
* Import a file.
|
||||
@@ -30,5 +30,6 @@ interface TrackImporter {
|
||||
* @param inputStream the file's input stream
|
||||
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT.
|
||||
*/
|
||||
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
|
||||
long importFile(InputStream inputStream);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
@@ -37,12 +33,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
@@ -50,6 +43,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
@@ -62,7 +56,6 @@ import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
import de.dennisguse.opentracks.util.TrackPointUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* A background service that registers a location listener and records track points.
|
||||
@@ -70,25 +63,19 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackRecordingService extends Service {
|
||||
public class TrackRecordingService extends Service implements HandlerServer.HandlerServerInterface {
|
||||
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
// The following variables are set in onCreate:
|
||||
@Deprecated //TODO Should not be necessary
|
||||
private ExecutorService locationExecutorService; // Enforces order of location changes.
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private LocationManager locationManager;
|
||||
private PeriodicTaskExecutor voiceExecutor;
|
||||
private TrackRecordingServiceNotificationManager notificationManager;
|
||||
private LocationListenerPolicy locationListenerPolicy;
|
||||
|
||||
private long recordingTrackId;
|
||||
private boolean recordingTrackPaused;
|
||||
private int recordingDistanceInterval;
|
||||
private int maxRecordingDistance;
|
||||
private int recordingGpsAccuracy;
|
||||
private long currentRecordingInterval;
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
@@ -110,27 +97,14 @@ public class TrackRecordingService extends Service {
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||
// Choose battery life over moving time accuracy.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(30 * UnitConversions.ONE_SECOND_MS, 5 * UnitConversions.ONE_MINUTE_MS, 5);
|
||||
} else if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptAccuracy(context)) {
|
||||
// Get all the updates.
|
||||
locationListenerPolicy = new AdaptiveLocationListenerPolicy(UnitConversions.ONE_SECOND_MS, 30 * UnitConversions.ONE_SECOND_MS, 0);
|
||||
} else {
|
||||
locationListenerPolicy = new AbsoluteLocationListenerPolicy(minRecordingInterval * UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
}
|
||||
|
||||
handlerServer.onSharedPreferenceChanged(context, preferences, key);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,43 +117,16 @@ public class TrackRecordingService extends Service {
|
||||
private boolean isIdle;
|
||||
|
||||
private TrackRecordingServiceBinder binder = new TrackRecordingServiceBinder(this);
|
||||
private final LocationListener locationListener = new LocationListener() {
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(final Location location) {
|
||||
if (locationExecutorService == null || locationExecutorService.isShutdown() || locationExecutorService.isTerminated()) {
|
||||
return;
|
||||
}
|
||||
locationExecutorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onLocationChangedAsync(location);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
Log.w(TAG, "LocationListener.onStatusChanged(): is not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
Log.w(TAG, "LocationListener.onProviderEnabled(): is not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
Log.w(TAG, "LocationListener.onProviderDisabled(): is not implemented.");
|
||||
}
|
||||
};
|
||||
private HandlerServer handlerServer;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
locationExecutorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
handlerServer = new HandlerServer(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -205,6 +152,8 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
handlerServer.stop(this);
|
||||
|
||||
if (remoteSensorManager != null) {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
@@ -213,9 +162,6 @@ public class TrackRecordingService extends Service {
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
|
||||
unregisterLocationListener();
|
||||
locationManager = null;
|
||||
|
||||
PreferencesUtils.unregister(this, sharedPreferenceChangeListener);
|
||||
|
||||
try {
|
||||
@@ -232,8 +178,6 @@ public class TrackRecordingService extends Service {
|
||||
// This should be the next to last operation
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
|
||||
// Shutdown the executorService last to avoid sending events to a dead executor.
|
||||
locationExecutorService.shutdown();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -433,7 +377,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
private void startGps() {
|
||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||
registerLocationListener();
|
||||
handlerServer.start(this);
|
||||
showNotification(true);
|
||||
}
|
||||
|
||||
@@ -503,6 +447,8 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
lastTrackPoint = null;
|
||||
|
||||
handlerServer.stop(this);
|
||||
|
||||
stopGps(trackStopped);
|
||||
}
|
||||
|
||||
@@ -514,7 +460,7 @@ public class TrackRecordingService extends Service {
|
||||
void stopGps(boolean shutdown) {
|
||||
if (!isRecording()) return;
|
||||
|
||||
unregisterLocationListener();
|
||||
handlerServer.stop(this);
|
||||
showNotification(false);
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
if (shutdown) {
|
||||
@@ -553,46 +499,27 @@ public class TrackRecordingService extends Service {
|
||||
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
|
||||
}
|
||||
|
||||
void onLocationChangedAsync(Location location) {
|
||||
@Override
|
||||
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
if (!isRecording() || isPaused()) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. Not recording or paused.");
|
||||
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. No track.");
|
||||
Log.w(TAG, "Ignore newTrackPoint. No track.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
Log.w(TAG, "Ignore onLocationChangedAsync. location is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
fillWithSensorDataSet(trackPoint);
|
||||
|
||||
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy)) {
|
||||
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
||||
return;
|
||||
}
|
||||
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
long idleTime = 0L;
|
||||
if (TrackPointUtils.after(trackPoint, lastValidTrackPoint)) {
|
||||
idleTime = trackPoint.getTime() - lastValidTrackPoint.getTime();
|
||||
}
|
||||
|
||||
locationListenerPolicy.updateIdleTime(idleTime);
|
||||
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
//Storing trackPoint
|
||||
|
||||
@@ -603,7 +530,7 @@ public class TrackRecordingService extends Service {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
|
||||
if (lastValidTrackPoint == null || !LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
|
||||
// For some reason the previous first trackPoint was not stored, but currentSegmentHasLocation set true.
|
||||
// Should not happen. The current segment should have a location. Just insert the current location.
|
||||
insertTrackPoint(track, trackPoint);
|
||||
@@ -719,34 +646,12 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long interval = locationListenerPolicy.getDesiredPollingInterval();
|
||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, locationListenerPolicy.getMinDistance_m(), locationListener);
|
||||
currentRecordingInterval = interval;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterLocationListener() {
|
||||
if (locationManager == null) {
|
||||
Log.e(TAG, "locationManager is null.");
|
||||
return;
|
||||
}
|
||||
locationManager.removeUpdates(locationListener);
|
||||
}
|
||||
|
||||
private void showNotification(boolean isGpsStarted) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingTrackId);
|
||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||
.addParentStack(TrackDetailActivity.class)
|
||||
.addParentStack(TrackRecordingActivity.class)
|
||||
.addNextIntent(intent)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
@@ -770,19 +675,6 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables processing of location updates from {@link android.location.LocationManager}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void enableLocationExecutor(boolean enable) {
|
||||
if (enable) {
|
||||
locationExecutorService = Executors.newSingleThreadExecutor();
|
||||
} else {
|
||||
locationExecutorService.shutdownNow();
|
||||
locationExecutorService = null;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.remoteSensorManager = remoteSensorManager;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user