forked from upstream-mirrors/OpenTracks
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 23175902fb | |||
| 706bd8b01f | |||
| dc4ac07e11 | |||
| 5be4129ba9 | |||
| f75a48ccab | |||
| 2815aa4b62 | |||
| 0383b0e1c7 | |||
| 1601741495 | |||
| be1fae956b | |||
| c7557d80bb | |||
| ac28124664 | |||
| 1deaf7650e | |||
| 9074fa1b6c | |||
| 5721a615a2 | |||
| fadcf1cd41 | |||
| a50b1ac78e | |||
| 9a7d4fdb36 | |||
| 9fb51a689d | |||
| 65bda35001 | |||
| 443ded86a5 | |||
| 83b5ebb02c | |||
| 240d8f6b6c | |||
| 39c5422341 | |||
| 4893a2b743 | |||
| 3408ea97f4 | |||
| ddb17f61cb | |||
| bbe182cf82 | |||
| b7b7bdf19b | |||
| 5681938a9e | |||
| 74835dcef5 | |||
| ed0dc05366 | |||
| 653eb8320a | |||
| 54b9c4370b | |||
| fa9cb3903c | |||
| 0f1d8500d1 | |||
| 20d7fd2c03 | |||
| 1d838344dc | |||
| fd826199e2 | |||
| f485d90ef2 | |||
| ec39e67a09 | |||
| 1a86db0a8b | |||
| 6a1fada22f | |||
| 2898e06246 | |||
| 0d87c65b49 | |||
| 9b97cdfcd1 | |||
| 95feae846a |
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [21, 29]
|
api-level: [29]
|
||||||
target: [default]
|
target: [default]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Free</th>
|
<th>Free</th>
|
||||||
<th>Paid</th>
|
<th>Paid (same as free)</th>
|
||||||
<th>Donations</th>
|
<th>Donations</th>
|
||||||
|
<th>Translations</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -24,39 +25,54 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.transifex.com/opentracks/opentracks/">
|
||||||
|
Transifex
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
* __Tracking:__ track your sport and outdoor activities
|
* __Tracking:__ track your sport and outdoor activities
|
||||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
||||||
* __Heart rate:__ supports Bluetooth LE sensors
|
* __Bluetooth LE sensors:__
|
||||||
|
* heart rate
|
||||||
|
* cycling: speed and cadence
|
||||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
||||||
* __No Internet access:__ Internet is not used
|
* __No Internet access:__ Internet is not used
|
||||||
* __Voice announcements__
|
* __Voice announcements__
|
||||||
* __No advertising__
|
* __No advertising__
|
||||||
* __Privacy:__
|
* __Privacy:__
|
||||||
* _does not contain any in-app analytics_
|
* _does not contain any in-app analytics_
|
||||||
* _does not have Google Play Services_
|
* _does not use Google Play Services_
|
||||||
* _has no cloud integration_
|
* _has no cloud integration_
|
||||||
|
|
||||||
__Only required permissions:__
|
__Only required permissions:__
|
||||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||||
|
|
||||||
_Please note:_
|
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
|
||||||
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
|
||||||
|
|
||||||
## Custom Dashboards
|
_Please note:_
|
||||||
|
_OpenTracks_ does not contain a _map_ (as this would required Internet access).
|
||||||
|
|
||||||
|
## Custom Dashboards (incl. Map)
|
||||||
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
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 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
|
## Screenshots
|
||||||
<div>
|
<div>
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||||
<img width="20%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||||
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_5.png">
|
||||||
|
<img width="15%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_6.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
@@ -77,7 +93,7 @@ Rework of _OpenTracks_ included:
|
|||||||
* removing calorie estimation and activity estimation,
|
* removing calorie estimation and activity estimation,
|
||||||
* removing support for ANT+ and Classic Bluetooth,
|
* removing support for ANT+ and Classic Bluetooth,
|
||||||
* adding support for Bluetooth LE heart rate sensors,
|
* 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.
|
* removing Android Service API for other apps.
|
||||||
|
|
||||||
Artwork, logos and user interface remained more or less unchanged.
|
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.
|
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).
|
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.
|
For the time being, the OpenTracks Data API can be used to implement mainly custom dashboards.
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
OpenTracks by default does not expose any data to 3rd-party applications.
|
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.
|
For each, one URI is provided.
|
||||||
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
These URIs notify observers if on content changes (e.g., a new `TrackPoint` was added).
|
||||||
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Craft new releases
|
## 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)
|
2. Decide on _version name_ (semantic versioning)
|
||||||
3. Manually update _version code_ and _version name_ in `build.gradle`
|
3. Manually update _version code_ and _version name_ in `build.gradle`
|
||||||
4. Create changelog (`_version code_.txt`)
|
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.
|
||||||
+15
-24
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 = { ->
|
def getVersionName = { ->
|
||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
@@ -50,25 +36,29 @@ def getVersionName = { ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
applicationLogo: "@drawable/ic_logo_color_24dp",
|
applicationLogo: "@mipmap/ic_launcher",
|
||||||
applicationName: "@string/app_name"
|
applicationName: "@string/app_name"
|
||||||
]
|
]
|
||||||
|
|
||||||
versionCode 3335
|
versionCode 3466
|
||||||
//versionCode getVersionCode() //Useful for debugging.
|
versionName "v3.8.0"
|
||||||
versionName "v3.6.2"
|
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -78,7 +68,7 @@ android {
|
|||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix "-debug"
|
versionNameSuffix "-debug"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
applicationLogo: "@drawable/ic_logo_black_24dp",
|
applicationLogo: "@mipmap/ic_launcher_debug",
|
||||||
applicationName: "OpenTracks (Debug)"
|
applicationName: "OpenTracks (Debug)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -100,8 +90,10 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
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 '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:core:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
@@ -113,5 +105,4 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation 'org.mockito:mockito-core:3.0.0'
|
testImplementation 'org.mockito:mockito-core:3.0.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-13
@@ -9,23 +9,35 @@ from every activity showing a menu.
|
|||||||
endlegend
|
endlegend
|
||||||
|
|
||||||
[*] --> TrackListActivity
|
[*] --> TrackListActivity
|
||||||
TrackListActivity --> TrackDetailActivity : Start recording
|
TrackListActivity --> TrackRecordingActivity : Start recording
|
||||||
TrackListActivity --> SearchListActivity
|
TrackListActivity --> SearchListActivity
|
||||||
TrackListActivity --> MarkerListActivity : Show all markers
|
TrackListActivity --> MarkerListActivity : Show all markers
|
||||||
TrackListActivity --> TrackDetailActivity : Show track
|
TrackListActivity --> AggregatedStatisticsActivity : Aggregated stats
|
||||||
|
TrackListActivity --> TrackRecordedActivity : Show track
|
||||||
TrackListActivity --> TrackDeleteActivity
|
TrackListActivity --> TrackDeleteActivity
|
||||||
|
|
||||||
SearchListActivity --> MarkerDetailActivity : Search result
|
SearchListActivity --> MarkerDetailActivity : Search result
|
||||||
SearchListActivity --> TrackDetailActivity : Search result
|
SearchListActivity --> TrackRecordedActivity : Search result
|
||||||
|
|
||||||
TrackDetailActivity --> TrackDeleteActivity
|
TrackRecordedActivity --> TrackDeleteActivity
|
||||||
TrackDetailActivity --> TrackEditActivity
|
TrackRecordedActivity --> TrackEditActivity
|
||||||
TrackDetailActivity --> MarkerListActivity
|
TrackRecordedActivity --> MarkerListActivity
|
||||||
TrackDetailActivity --> MarkerEditActivity : Add marker
|
TrackRecordedActivity --> MarkerEditActivity : Add marker
|
||||||
TrackDetailActivity --> CameraIntent : Add marker (with photo)
|
TrackRecordedActivity --> CameraIntent : Add marker (with photo)
|
||||||
TrackDetailActivity --> ShareFileIntent
|
TrackRecordedActivity --> ShareFileIntent
|
||||||
TrackDetailActivity --> DashboardIntent : Database cursor
|
TrackRecordedActivity --> DashboardIntent : Database cursor
|
||||||
TrackDetailActivity --> TrackOnMapIntent : KMZ
|
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
|
MarkerListActivity --> MarkerDetailActivity
|
||||||
|
|
||||||
@@ -35,6 +47,9 @@ MarkerDetailActivity --> CoordinateOnMapIntent : URI
|
|||||||
SettingsActivity --> AboutActivity
|
SettingsActivity --> AboutActivity
|
||||||
SettingsActivity --> HelpActivity
|
SettingsActivity --> HelpActivity
|
||||||
SettingsActivity --> ExportActivity
|
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 |
@@ -4,6 +4,7 @@ Erfasst deine Kilometer, ohne dass es jemand anderes tut.
|
|||||||
Funktionen & Features:
|
Funktionen & Features:
|
||||||
* zeichnet Sport und andere Outdooraktivitäten auf
|
* zeichnet Sport und andere Outdooraktivitäten auf
|
||||||
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
|
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
|
||||||
* zeichnet deine Herzfrequenz auf (unterstützt Bluetooth-LE-Sensoren)
|
* zeichnet deine Herzfrequenz auf (Bluetooth LE)
|
||||||
|
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren (Bluetooth LE)
|
||||||
* exportiert deine Daten als KML oder GPX
|
* exportiert deine Daten als KML oder GPX
|
||||||
* funktioniert ganz ohne Internet
|
* funktioniert ganz ohne Internet
|
||||||
@@ -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,13 @@
|
|||||||
|
v3.7.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Report speed/pace by activity type
|
||||||
|
- Added workaround for GPS week rollover
|
||||||
|
- (Experimental) Support for Bluetooth LE cycling sensors (cadence and speed)
|
||||||
|
- KMZ/KML import enhanced
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- KMZ import: only import used images
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Refactored TrackRecordingService
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.7.1: OpenTracks
|
||||||
|
|
||||||
|
Workaround:
|
||||||
|
- Hide start GPS button
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fixed null pointer
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
v3.7.2: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Re-added start GPS button
|
||||||
|
- Track name is not overwritten at the end of a recording
|
||||||
|
- Markers: removing a photo also deletes the file
|
||||||
@@ -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
|
||||||
@@ -3,6 +3,8 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
|||||||
Features:
|
Features:
|
||||||
* Sport tracking: track your sport and outdoor activities
|
* Sport tracking: track your sport and outdoor activities
|
||||||
* Photos and markers: mark interesting locations while tracking
|
* Photos and markers: mark interesting locations while tracking
|
||||||
* Heart rate: supports Bluetooth LE sensors
|
* Supports Bluetooth LE sensors:
|
||||||
|
* heart rate
|
||||||
|
* cycling: speed and cadence
|
||||||
* Export data: export tracks either as KML or GPX
|
* Export data: export tracks either as KML or GPX
|
||||||
* No Internet access: Internet is not used
|
* No Internet access: Internet is not used
|
||||||
|
|||||||
@@ -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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -129,6 +130,7 @@ fi
|
|||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -175,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# 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"
|
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" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
Vendored
+104
-100
@@ -1,100 +1,104 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem Copyright 2015 the original author or authors.
|
@rem Copyright 2015 the original author or authors.
|
||||||
@rem
|
@rem
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
@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 not use this file except in compliance with the License.
|
||||||
@rem You may obtain a copy of the License at
|
@rem You may obtain a copy of the License at
|
||||||
@rem
|
@rem
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
@rem
|
@rem
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
@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 distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
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.
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
@rem Find java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
echo.
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo.
|
||||||
echo location of your Java installation.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
goto fail
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
goto fail
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
if exist "%JAVA_EXE%" goto init
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
if exist "%JAVA_EXE%" goto init
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo.
|
||||||
echo location of your Java installation.
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
goto fail
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
goto fail
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
:win9xME_args_slurp
|
set CMD_LINE_ARGS=
|
||||||
if "x%~1" == "x" goto execute
|
set _SKIP=2
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
@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%
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Execute Gradle
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
:fail
|
:end
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
@rem End local scope for the variables with windows NT shell
|
||||||
rem the _cmd.exe /c_ return code!
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
:mainEnd
|
rem the _cmd.exe /c_ return code!
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
:omega
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ package de.dennisguse.opentracks;
|
|||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commons utilities for creating stubs of track, location.
|
* Commons utilities for creating stubs of track, location.
|
||||||
@@ -37,23 +35,6 @@ public class TrackStubUtils {
|
|||||||
private static final float INITIAL_ACCURACY = 5;
|
private static final float INITIAL_ACCURACY = 5;
|
||||||
private static final float INITIAL_SPEED = 10;
|
private static final float INITIAL_SPEED = 10;
|
||||||
private static final float INITIAL_BEARING = 3.0f;
|
private static final float INITIAL_BEARING = 3.0f;
|
||||||
// Used to change the value of latitude, longitude, and altitude.
|
|
||||||
private static final double DIFFERENCE = 0.01;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a a {@link Track} stub with specified number of locations.
|
|
||||||
*
|
|
||||||
* @param numberOfLocations the number of locations for the track
|
|
||||||
* @return a track stub.
|
|
||||||
*/
|
|
||||||
public static Track createTrack(int numberOfLocations) {
|
|
||||||
Track track = new Track();
|
|
||||||
for (int i = 0; i < numberOfLocations; i++) {
|
|
||||||
track.addTrackPoint(createDefaultTrackPoint(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
|
||||||
}
|
|
||||||
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a MyTracks location with default values.
|
* Create a MyTracks location with default values.
|
||||||
@@ -79,6 +60,6 @@ public class TrackStubUtils {
|
|||||||
location.setTime(INITIAL_TIME);
|
location.setTime(INITIAL_TIME);
|
||||||
location.setBearing(INITIAL_BEARING);
|
location.setBearing(INITIAL_BEARING);
|
||||||
|
|
||||||
return new TrackPoint(location, new SensorDataSet(Float.NaN, Float.NaN));
|
return new TrackPoint(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,40 @@ public class DescriptionGeneratorTest {
|
|||||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateTrackDescriptionWithoutMaxMinElevation() {
|
||||||
|
Track track = new Track();
|
||||||
|
TrackStatistics stats = new TrackStatistics();
|
||||||
|
stats.setTotalDistance(20000);
|
||||||
|
stats.setTotalTime(600000);
|
||||||
|
stats.setMovingTime(300000);
|
||||||
|
stats.setMaxSpeed(100);
|
||||||
|
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
||||||
|
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
||||||
|
stats.setTotalElevationGain(6000);
|
||||||
|
stats.setStartTime_ms(START_TIME);
|
||||||
|
track.setTrackStatistics(stats);
|
||||||
|
track.setCategory("hiking");
|
||||||
|
String expected = //"Created by"
|
||||||
|
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
|
||||||
|
+ "Name: -<br>"
|
||||||
|
+ "Activity type: hiking<br>"
|
||||||
|
+ "Description: -<br>"
|
||||||
|
+ "Total distance: 20.00 km (12.4 mi)<br>"
|
||||||
|
+ "Total time: 10:00<br>"
|
||||||
|
+ "Moving time: 05:00<br>"
|
||||||
|
+ "Average speed: 120.00 km/h (74.6 mi/h)<br>"
|
||||||
|
+ "Average moving speed: 240.00 km/h (149.1 mi/h)<br>"
|
||||||
|
+ "Max speed: 360.00 km/h (223.7 mi/h)<br>"
|
||||||
|
+ "Average pace: 0:30 min/km (0:48 min/mi)<br>"
|
||||||
|
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
|
||||||
|
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
||||||
|
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||||
|
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||||
|
|
||||||
|
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
|
* Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}.
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public class SearchEngineTest {
|
|||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
providerUtils = new ContentProviderUtils(context);
|
providerUtils = new ContentProviderUtils(context);
|
||||||
@@ -70,7 +69,7 @@ public class SearchEngineTest {
|
|||||||
providerUtils.deleteAllTracks(context);
|
providerUtils.deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long insertTrack(String title, String description, String category, double distance, long hoursAgo) {
|
private long insertTrack(String title, String description, String category, long hoursAgo) {
|
||||||
Track track = new Track();
|
Track track = new Track();
|
||||||
track.setName(title);
|
track.setName(title);
|
||||||
track.setDescription(description);
|
track.setDescription(description);
|
||||||
@@ -88,15 +87,11 @@ public class SearchEngineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long insertTrack(String title, String description, String category) {
|
private long insertTrack(String title, String description, String category) {
|
||||||
return insertTrack(title, description, category, 0, -1);
|
return insertTrack(title, description, category, -1);
|
||||||
}
|
|
||||||
|
|
||||||
private long insertTrack(String title, double distance) {
|
|
||||||
return insertTrack(title, "", "", distance, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long insertTrack(String title, long hoursAgo) {
|
private long insertTrack(String title, long hoursAgo) {
|
||||||
return insertTrack(title, "", "", 0.0, hoursAgo);
|
return insertTrack(title, "", "", hoursAgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
|
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
package de.dennisguse.opentracks.content.data;
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
public class TestDataUtil {
|
public class TestDataUtil {
|
||||||
|
|
||||||
@@ -8,36 +17,89 @@ public class TestDataUtil {
|
|||||||
public static final double INITIAL_LONGITUDE = -57.0;
|
public static final double INITIAL_LONGITUDE = -57.0;
|
||||||
public static final double ALTITUDE_INTERVAL = 2.5;
|
public static final double ALTITUDE_INTERVAL = 2.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a track without any trackPoints.
|
||||||
|
*/
|
||||||
|
public static Track createTrack(long trackId) {
|
||||||
|
Track track = new Track();
|
||||||
|
track.setId(trackId);
|
||||||
|
track.setName("Test: " + trackId);
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates a track which is used for testing.
|
* Simulates a track which is used for testing.
|
||||||
*
|
*
|
||||||
* @param id the id of the track
|
* @param trackId the trackId of the track
|
||||||
* @param numPoints the location number in the track
|
* @param numPoints the trackPoints number in the track
|
||||||
* @return the simulated track
|
|
||||||
*/
|
*/
|
||||||
public static Track getTrack(long id, int numPoints) {
|
public static Pair<Track, TrackPoint[]> createTrack(long trackId, int numPoints) {
|
||||||
Track track = new Track();
|
Track track = createTrack(trackId);
|
||||||
track.setId(id);
|
|
||||||
track.setName("Test: " + id);
|
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
||||||
for (int i = 0; i < numPoints; i++) {
|
for (int i = 0; i < numPoints; i++) {
|
||||||
track.addTrackPoint(createTrackPoint(i));
|
trackPoints[i] = createTrackPoint(i);
|
||||||
}
|
}
|
||||||
return track;
|
|
||||||
|
return new Pair<>(track, trackPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, long trackId, int numPoints) {
|
||||||
|
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
|
||||||
|
|
||||||
|
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
|
||||||
|
|
||||||
|
return pair.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a location.
|
* Creates a location.
|
||||||
*
|
*
|
||||||
* @param i the index to set the value of location.
|
* @param i the index for the TrackPoint.
|
||||||
* @return created location
|
|
||||||
*/
|
*/
|
||||||
public static TrackPoint createTrackPoint(int i) {
|
public static TrackPoint createTrackPoint(int i) {
|
||||||
Location location = new Location("test");
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
location.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||||
location.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||||
location.setAccuracy((float) i / 100.0f);
|
trackPoint.setAccuracy((float) i / 100.0f);
|
||||||
location.setAltitude(i * ALTITUDE_INTERVAL);
|
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||||
location.setTime(i + 1);
|
trackPoint.setTime(i + 1);
|
||||||
return new TrackPoint(location);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a track with locations into the database.
|
||||||
|
*
|
||||||
|
* @param track track to be inserted
|
||||||
|
* @param trackPoints trackPoints to be inserted
|
||||||
|
*/
|
||||||
|
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
|
||||||
|
contentProviderUtils.insertTrack(track);
|
||||||
|
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Waypoint with a photo.
|
||||||
|
*
|
||||||
|
* @param context The context.
|
||||||
|
* @param trackId The track id.
|
||||||
|
* @param location The location.
|
||||||
|
* @return the Waypoint created.
|
||||||
|
*/
|
||||||
|
public static Waypoint createWaypointWithPhoto(Context context, long trackId, Location location) throws IOException {
|
||||||
|
String photoUrl = "";
|
||||||
|
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
|
||||||
|
dstFile.createNewFile();
|
||||||
|
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
||||||
|
photoUrl = photoUri.toString();
|
||||||
|
|
||||||
|
return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+431
-174
@@ -21,6 +21,7 @@ import android.content.ContentValues;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
@@ -31,8 +32,12 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
@@ -42,6 +47,8 @@ import de.dennisguse.opentracks.content.data.TracksColumns;
|
|||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -57,6 +64,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
private static final String MOCK_DESC = "Mock Next Waypoint Desc!";
|
||||||
private static final String TEST_DESC = "Test Desc!";
|
private static final String TEST_DESC = "Test Desc!";
|
||||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||||
|
private static final String TEST_NAME_NEW = "Test Name new!";
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
@@ -141,7 +149,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
loc.setAltitude(i * 2.5);
|
loc.setAltitude(i * 2.5);
|
||||||
trackPoints[i] = new TrackPoint(loc);
|
trackPoints[i] = new TrackPoint(loc);
|
||||||
}
|
}
|
||||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, numPoints, id);
|
contentProviderUtils.bulkInsertTrackPoint(trackPoints, id);
|
||||||
|
|
||||||
// Load all inserted trackPoints.
|
// Load all inserted trackPoints.
|
||||||
long lastPointId = -1;
|
long lastPointId = -1;
|
||||||
@@ -165,22 +173,26 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrack() {
|
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();
|
long trackId = System.currentTimeMillis();
|
||||||
columnIndex = startColumnIndex;
|
|
||||||
|
int columnIndex = 1;
|
||||||
// Id
|
// 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
|
// Name
|
||||||
|
columnIndex++;
|
||||||
String name = NAME_PREFIX + trackId;
|
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);
|
Track track = contentProviderUtils.createTrack(cursorMock);
|
||||||
Assert.assertEquals(trackId, track.getId());
|
Assert.assertEquals(trackId, track.getId());
|
||||||
@@ -194,10 +206,11 @@ public class CustomContentProviderUtilsTest {
|
|||||||
public void testDeleteAllTracks() {
|
public void testDeleteAllTracks() {
|
||||||
// Insert track, points and waypoint at first.
|
// Insert track, points and waypoint at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
|
||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
contentProviderUtils.insertWaypoint(waypoint);
|
contentProviderUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
Assert.assertEquals(1, tracksCursor.getCount());
|
Assert.assertEquals(1, tracksCursor.getCount());
|
||||||
@@ -216,20 +229,58 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Assert.assertEquals(0, waypointCursor.getCount());
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
|
||||||
|
// Insert track, points and waypoint with photo at first.
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
contentProviderUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
|
Assert.assertEquals(1, tracksCursor.getCount());
|
||||||
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
|
Assert.assertEquals(10, tracksPointsCursor.getCount());
|
||||||
|
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
|
Assert.assertEquals(1, waypointCursor.getCount());
|
||||||
|
// Check waypoint has photo and it's in the external storage.
|
||||||
|
Assert.assertTrue(waypoint.hasPhoto());
|
||||||
|
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
// Delete all.
|
||||||
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
|
// Check whether all have been deleted.
|
||||||
|
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
|
Assert.assertEquals(0, tracksCursor.getCount());
|
||||||
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
|
Assert.assertEquals(0, tracksPointsCursor.getCount());
|
||||||
|
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
|
Assert.assertFalse(dir.exists());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTrack() {
|
public void testDeleteTrack() {
|
||||||
// Insert three tracks, points of two tracks and way point of one track.
|
// Insert three tracks, points of two tracks and way point of one track.
|
||||||
|
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 0);
|
||||||
|
|
||||||
contentProviderUtils.insertTrack(track);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
|
||||||
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 1, 10));
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
|
||||||
insertTrackWithLocations(TestDataUtil.getTrack(trackId + 2, 10));
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId + 1));
|
||||||
waypoint.setTrackId(trackId);
|
waypoint.setTrackId(trackId);
|
||||||
contentProviderUtils.insertWaypoint(waypoint);
|
contentProviderUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
@@ -251,15 +302,70 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Assert.assertEquals(0, waypointCursor.getCount());
|
Assert.assertEquals(0, waypointCursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteTrack_withWaypointPhoto() throws IOException {
|
||||||
|
// Insert three tracks.
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
|
||||||
|
|
||||||
|
// Insert a waypoint in tracks trackId and trackId + 1.
|
||||||
|
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint1.getLocation());
|
||||||
|
contentProviderUtils.insertWaypoint(waypoint1);
|
||||||
|
File dir1 = FileUtils.getPhotoDir(context, trackId);
|
||||||
|
|
||||||
|
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId + 1);
|
||||||
|
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId + 1, trackPoint2.getLocation());
|
||||||
|
contentProviderUtils.insertWaypoint(waypoint2);
|
||||||
|
File dir2 = FileUtils.getPhotoDir(context, trackId + 1);
|
||||||
|
|
||||||
|
// Check.
|
||||||
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
|
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
|
Assert.assertEquals(3, tracksCursor.getCount());
|
||||||
|
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
|
Assert.assertEquals(30, tracksPointsCursor.getCount());
|
||||||
|
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
|
Assert.assertEquals(2, waypointCursor.getCount());
|
||||||
|
Assert.assertTrue(waypoint1.hasPhoto());
|
||||||
|
Assert.assertTrue(dir1.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir1.list().length);
|
||||||
|
Assert.assertTrue(dir1.exists());
|
||||||
|
Assert.assertTrue(dir2.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir2.list().length);
|
||||||
|
Assert.assertTrue(dir2.exists());
|
||||||
|
// Delete one track.
|
||||||
|
contentProviderUtils.deleteTrack(context, trackId);
|
||||||
|
// Check whether all data of a track has been deleted.
|
||||||
|
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
|
||||||
|
Assert.assertEquals(2, tracksCursor.getCount());
|
||||||
|
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
|
||||||
|
Assert.assertEquals(20, tracksPointsCursor.getCount());
|
||||||
|
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
|
||||||
|
Assert.assertEquals(1, waypointCursor.getCount());
|
||||||
|
Assert.assertFalse(dir1.exists());
|
||||||
|
Assert.assertTrue(dir2.exists());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
* Tests the method {@link ContentProviderUtils#getAllTracks()}
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetAllTracks() {
|
public void testGetAllTracks() {
|
||||||
|
// given
|
||||||
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
|
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||||
|
|
||||||
|
// when
|
||||||
List<Track> allTracks = contentProviderUtils.getAllTracks();
|
List<Track> allTracks = contentProviderUtils.getAllTracks();
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
|
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
|
||||||
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
|
||||||
}
|
}
|
||||||
@@ -270,7 +376,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetLastTrack() {
|
public void testGetLastTrack() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||||
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
|
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +386,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetTrack() {
|
public void testGetTrack() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
contentProviderUtils.insertTrack(TestDataUtil.getTrack(trackId, 0));
|
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
|
||||||
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
|
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,11 +395,14 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateTrack() {
|
public void testUpdateTrack() {
|
||||||
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 0);
|
Track track = TestDataUtil.createTrack(trackId);
|
||||||
String nameOld = "name1";
|
String nameOld = "name1";
|
||||||
String nameNew = "name2";
|
String nameNew = "name2";
|
||||||
track.setName(nameOld);
|
track.setName(nameOld);
|
||||||
|
|
||||||
|
// when / then
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track);
|
||||||
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
|
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
|
||||||
track.setName(nameNew);
|
track.setName(nameNew);
|
||||||
@@ -307,7 +416,8 @@ public class CustomContentProviderUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCreateContentValues_waypoint() {
|
public void testCreateContentValues_waypoint() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
|
|
||||||
// Bottom
|
// Bottom
|
||||||
long startTime = 1000L;
|
long startTime = 1000L;
|
||||||
// AverageSpeed
|
// AverageSpeed
|
||||||
@@ -322,17 +432,10 @@ public class CustomContentProviderUtilsTest {
|
|||||||
statistics.setMaxElevation(1250.0);
|
statistics.setMaxElevation(1250.0);
|
||||||
statistics.setMinElevation(1200.0);
|
statistics.setMinElevation(1200.0);
|
||||||
|
|
||||||
track.setTrackStatistics(statistics);
|
track.first.setTrackStatistics(statistics);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
// Insert at first.
|
Waypoint waypoint = new Waypoint(track.second[0]);
|
||||||
Location location = new Location("test");
|
|
||||||
location.setLatitude(22);
|
|
||||||
location.setLongitude(22);
|
|
||||||
location.setAccuracy((float) 1 / 100.0f);
|
|
||||||
location.setAltitude(2.5);
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint(location);
|
|
||||||
waypoint.setDescription(TEST_DESC);
|
waypoint.setDescription(TEST_DESC);
|
||||||
contentProviderUtils.insertWaypoint(waypoint);
|
contentProviderUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
@@ -342,7 +445,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
waypoint.setId(waypointId);
|
waypoint.setId(waypointId);
|
||||||
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
|
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
|
||||||
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
|
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
|
||||||
Assert.assertEquals(22 * 1000000, contentValues.get(WaypointsColumns.LONGITUDE));
|
Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
|
||||||
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
|
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,66 +485,106 @@ public class CustomContentProviderUtilsTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtils#deleteWaypoint(long)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
|
||||||
* when there is only one waypoint in the track.
|
* when there is only one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteWaypoint_onlyOneWayPoint() {
|
public void testDeleteWaypoint_onlyOneWayPoint() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Waypoint waypoint1 = new Waypoint();
|
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint1.setDescription(TEST_DESC);
|
waypoint1.setDescription(TEST_DESC);
|
||||||
waypoint1.setTrackId(trackId);
|
waypoint1.setTrackId(trackId);
|
||||||
contentProviderUtils.insertWaypoint(waypoint1);
|
contentProviderUtils.insertWaypoint(waypoint1);
|
||||||
|
|
||||||
// Delete
|
// Check insert was done.
|
||||||
contentProviderUtils.deleteWaypoint(1);
|
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
|
||||||
|
|
||||||
Assert.assertNull(contentProviderUtils.getWaypoint(1));
|
// Get waypoint id that needs to delete.
|
||||||
|
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||||
|
|
||||||
|
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method
|
* Tests the method
|
||||||
* {@link ContentProviderUtils#deleteWaypoint(long)}
|
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
|
||||||
* when there is more than one waypoint in the track.
|
* when there is only one waypoint in the track.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert at first.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
contentProviderUtils.insertWaypoint(waypoint1);
|
||||||
|
|
||||||
|
// Check insert was done.
|
||||||
|
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
|
||||||
|
|
||||||
|
// Get waypoint id that needs to delete.
|
||||||
|
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||||
|
|
||||||
|
// Check waypoint has photo and it's in the external storage.
|
||||||
|
Assert.assertTrue(waypoint1.hasPhoto());
|
||||||
|
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||||
|
|
||||||
|
// Check waypoint doesn't exists and photo folder was deleted.
|
||||||
|
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||||
|
Assert.assertFalse(dir.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, long)} when there is more than one waypoint in the track.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteWaypoint_hasNextWayPoint() {
|
public void testDeleteWaypoint_hasNextWayPoint() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
|
||||||
statistics.setStartTime_ms(1000L);
|
|
||||||
statistics.setStopTime_ms(2500L);
|
|
||||||
statistics.setTotalTime(1500L);
|
|
||||||
statistics.setMovingTime(700L);
|
|
||||||
statistics.setTotalDistance(750.0);
|
|
||||||
statistics.setTotalElevationGain(50.0);
|
|
||||||
statistics.setMaxSpeed(60.0);
|
|
||||||
statistics.setMaxElevation(1250.0);
|
|
||||||
statistics.setMinElevation(1200.0);
|
|
||||||
|
|
||||||
track.setTrackStatistics(statistics);
|
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
|
|
||||||
|
// Track track = TestDataUtil.createTrackAndInsert(trackId, 10);
|
||||||
|
//
|
||||||
|
// TrackStatistics statistics = new TrackStatistics();
|
||||||
|
// statistics.setStartTime_ms(1000L);
|
||||||
|
// statistics.setStopTime_ms(2500L);
|
||||||
|
// statistics.setTotalTime(1500L);
|
||||||
|
// statistics.setMovingTime(700L);
|
||||||
|
// statistics.setTotalDistance(750.0);
|
||||||
|
// statistics.setTotalElevationGain(50.0);
|
||||||
|
// statistics.setMaxSpeed(60.0);
|
||||||
|
// statistics.setMaxElevation(1250.0);
|
||||||
|
// statistics.setMinElevation(1200.0);
|
||||||
|
//
|
||||||
|
// track.setTrackStatistics(statistics);
|
||||||
|
// TestDataUtil.insertTrackWithLocations(contentProviderUtils, track);
|
||||||
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Waypoint waypoint1 = new Waypoint();
|
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint1.setDescription(MOCK_DESC);
|
waypoint1.setDescription(MOCK_DESC);
|
||||||
waypoint1.setTrackId(trackId);
|
waypoint1.setTrackId(trackId);
|
||||||
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
|
||||||
|
|
||||||
Waypoint waypoint2 = new Waypoint();
|
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint2.setDescription(MOCK_DESC);
|
waypoint2.setDescription(MOCK_DESC);
|
||||||
waypoint2.setTrackId(trackId);
|
waypoint2.setTrackId(trackId);
|
||||||
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
|
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||||
contentProviderUtils.deleteWaypoint(waypoint1Id);
|
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
|
||||||
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
|
||||||
|
|
||||||
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
|
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
|
||||||
@@ -453,16 +596,15 @@ public class CustomContentProviderUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetNextWaypointNumber() {
|
public void testGetNextWaypointNumber() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
|
|
||||||
Waypoint waypoint1 = new Waypoint();
|
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint1.setTrackId(trackId);
|
waypoint1.setTrackId(trackId);
|
||||||
Waypoint waypoint2 = new Waypoint();
|
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint2.setTrackId(trackId);
|
waypoint2.setTrackId(trackId);
|
||||||
Waypoint waypoint3 = new Waypoint();
|
Waypoint waypoint3 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint3.setTrackId(trackId);
|
waypoint3.setTrackId(trackId);
|
||||||
Waypoint waypoint4 = new Waypoint();
|
Waypoint waypoint4 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint4.setTrackId(trackId);
|
waypoint4.setTrackId(trackId);
|
||||||
contentProviderUtils.insertWaypoint(waypoint1);
|
contentProviderUtils.insertWaypoint(waypoint1);
|
||||||
contentProviderUtils.insertWaypoint(waypoint2);
|
contentProviderUtils.insertWaypoint(waypoint2);
|
||||||
@@ -479,10 +621,9 @@ public class CustomContentProviderUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testInsertAndGetWaypoint() {
|
public void testInsertAndGetWaypoint() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint.setDescription(TEST_DESC);
|
waypoint.setDescription(TEST_DESC);
|
||||||
waypoint.setTrackId(trackId);
|
waypoint.setTrackId(trackId);
|
||||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
@@ -491,15 +632,15 @@ public class CustomContentProviderUtilsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}.
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateWaypoint() {
|
public void testUpdateWaypoint() {
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
// Insert at first.
|
// Insert at first.
|
||||||
Waypoint waypoint = new Waypoint();
|
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
|
||||||
waypoint.setDescription(TEST_DESC);
|
waypoint.setDescription(TEST_DESC);
|
||||||
waypoint.setTrackId(trackId);
|
waypoint.setTrackId(trackId);
|
||||||
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
@@ -507,84 +648,178 @@ public class CustomContentProviderUtilsTest {
|
|||||||
// Update
|
// Update
|
||||||
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
waypoint.setDescription(TEST_DESC_NEW);
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
contentProviderUtils.updateWaypoint(waypoint);
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], int, long)}.
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_withPhoto() throws IOException {
|
||||||
|
// tests after update waypoint with photo the photo remains in the storage.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert at first.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
|
// Update
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setName(TEST_NAME_NEW);
|
||||||
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
|
||||||
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertTrue(waypoint.hasPhoto());
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
|
||||||
|
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert at first.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
|
||||||
|
// Update
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setName(TEST_NAME_NEW);
|
||||||
|
waypoint.setDescription(TEST_DESC_NEW);
|
||||||
|
waypoint.setPhotoUrl(null);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
|
||||||
|
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertFalse(waypoint.hasPhoto());
|
||||||
|
Assert.assertFalse(dir.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#updateWaypoint(Context, Waypoint)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
|
||||||
|
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
|
||||||
|
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
// Insert two waypoints with photos.
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
|
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
waypoint.setDescription(TEST_DESC);
|
||||||
|
waypoint.setTrackId(trackId);
|
||||||
|
Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
|
||||||
|
otherWaypoint.setDescription(TEST_DESC);
|
||||||
|
otherWaypoint.setTrackId(trackId);
|
||||||
|
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
|
||||||
|
long otherWaypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(otherWaypoint));
|
||||||
|
|
||||||
|
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(2, dir.list().length);
|
||||||
|
|
||||||
|
// Update one waypoint deleting photo.
|
||||||
|
waypoint = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
waypoint.setPhotoUrl(null);
|
||||||
|
contentProviderUtils.updateWaypoint(context, waypoint);
|
||||||
|
|
||||||
|
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
|
||||||
|
Assert.assertFalse(waypoint.hasPhoto());
|
||||||
|
Assert.assertTrue(dir.exists());
|
||||||
|
Assert.assertTrue(dir.isDirectory());
|
||||||
|
Assert.assertEquals(1, dir.list().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBulkInsertTrackPoint() {
|
public void testBulkInsertTrackPoint() {
|
||||||
// Insert track, point at first.
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
|
||||||
|
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), -1, trackId);
|
// when / then
|
||||||
|
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
|
||||||
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), 8, trackId);
|
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(track.second, 0, 8), trackId);
|
||||||
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
* Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}.
|
||||||
*/
|
*/
|
||||||
|
//TODO incomplete
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTrackPoint() {
|
public void testCreateTrackPoint() {
|
||||||
// Set index.
|
// given
|
||||||
int index = 1;
|
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(1);
|
||||||
// Id
|
|
||||||
when(cursorMock.getColumnIndex(TrackPointsColumns._ID)).thenReturn(index++);
|
|
||||||
// Longitude
|
|
||||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(index++);
|
|
||||||
// Latitude
|
|
||||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(index++);
|
|
||||||
// Time
|
|
||||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME))
|
|
||||||
.thenReturn(index++);
|
|
||||||
// Speed
|
|
||||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(index++);
|
|
||||||
// Sensor
|
|
||||||
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(index++);
|
|
||||||
|
|
||||||
// Set return value of isNull().
|
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE)).thenReturn(2);
|
||||||
index = 2;
|
when(cursorMock.isNull(2)).thenReturn(false);
|
||||||
// Longitude
|
|
||||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
|
||||||
// Latitude
|
|
||||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
|
||||||
// Time
|
|
||||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
|
||||||
// Speed
|
|
||||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
|
||||||
// Sensor
|
|
||||||
when(cursorMock.isNull(index++)).thenReturn(false);
|
|
||||||
|
|
||||||
// Set return value of isNull().
|
|
||||||
index = 2;
|
|
||||||
// Longitude
|
|
||||||
int longitude = 11;
|
int longitude = 11;
|
||||||
when(cursorMock.getInt(index++)).thenReturn(longitude * 1000000);
|
when(cursorMock.getInt(2)).thenReturn(longitude * 1000000);
|
||||||
// Latitude.
|
|
||||||
int latitude = 22;
|
|
||||||
when(cursorMock.getInt(index++)).thenReturn(latitude * 1000000);
|
|
||||||
// Time
|
|
||||||
long time = System.currentTimeMillis();
|
|
||||||
when(cursorMock.getLong(index++)).thenReturn(time);
|
|
||||||
// Speed
|
|
||||||
float speed = 2.2f;
|
|
||||||
when(cursorMock.getFloat(index++)).thenReturn(speed);
|
|
||||||
// Sensor
|
|
||||||
byte[] sensor = "Sensor state".getBytes();
|
|
||||||
when(cursorMock.getBlob(index++)).thenReturn(sensor);
|
|
||||||
|
|
||||||
TrackPoint location = contentProviderUtils.createTrackPoint(cursorMock);
|
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE)).thenReturn(3);
|
||||||
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
|
when(cursorMock.isNull(3)).thenReturn(false);
|
||||||
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
|
int latitude = 22;
|
||||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
when(cursorMock.getInt(3)).thenReturn(latitude * 1000000);
|
||||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
|
||||||
|
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.TIME)).thenReturn(4);
|
||||||
|
when(cursorMock.isNull(4)).thenReturn(false);
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
when(cursorMock.getLong(4)).thenReturn(time);
|
||||||
|
|
||||||
|
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SPEED)).thenReturn(5);
|
||||||
|
when(cursorMock.isNull(5)).thenReturn(false);
|
||||||
|
float speed = 2.2f;
|
||||||
|
when(cursorMock.getFloat(5)).thenReturn(speed);
|
||||||
|
|
||||||
|
when(cursorMock.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE)).thenReturn(6);
|
||||||
|
when(cursorMock.isNull(6)).thenReturn(false);
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackPoint trackPoint = contentProviderUtils.createTrackPoint(cursorMock);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(longitude, trackPoint.getLongitude(), 0.01);
|
||||||
|
Assert.assertEquals(latitude, trackPoint.getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(time, trackPoint.getTime(), 0.01);
|
||||||
|
Assert.assertEquals(speed, trackPoint.getSpeed(), 0.01);
|
||||||
|
Assert.assertFalse(trackPoint.hasHeartRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -595,11 +830,32 @@ public class CustomContentProviderUtilsTest {
|
|||||||
public void testInsertTrackPoint() {
|
public void testInsertTrackPoint() {
|
||||||
// Insert track, point at first.
|
// Insert track, point at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
|
||||||
|
|
||||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
||||||
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
Assert.assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertAndLoadTrackPoint() {
|
||||||
|
// given
|
||||||
|
long trackId = System.currentTimeMillis();
|
||||||
|
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
|
|
||||||
|
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
|
||||||
|
trackPoint.setHeartRate_bpm(1F);
|
||||||
|
trackPoint.setCyclingCadence_rpm(2F);
|
||||||
|
trackPoint.setPower(3F);
|
||||||
|
|
||||||
|
// when
|
||||||
|
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||||
|
|
||||||
|
// then
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertTrue(trackPoints.get(10).hasHeartRate());
|
||||||
|
Assert.assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
||||||
|
Assert.assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
|
||||||
|
Assert.assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -609,8 +865,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
public void testGetLastValidTrackPoint() {
|
public void testGetLastValidTrackPoint() {
|
||||||
// Insert track, points at first.
|
// Insert track, points at first.
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
|
||||||
insertTrackWithLocations(track);
|
|
||||||
|
|
||||||
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||||
checkLocation(9, lastTrackPoint.getLocation());
|
checkLocation(9, lastTrackPoint.getLocation());
|
||||||
@@ -621,17 +876,20 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_desc() {
|
public void testGetTrackPointCursor_desc() {
|
||||||
// Insert track, points at first.
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
long[] trackpointIds = new long[track.second.length];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertEquals(2, cursor.getCount());
|
Assert.assertEquals(2, cursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,17 +898,20 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointCursor_asc() {
|
public void testGetTrackPointCursor_asc() {
|
||||||
// Insert track, points at first.
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
long[] trackpointIds = new long[track.second.length];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertEquals(2, cursor.getCount());
|
Assert.assertEquals(2, cursor.getCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,19 +920,21 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_desc() {
|
public void testGetTrackPointLocationIterator_desc() {
|
||||||
// Insert track, points at first.
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
long[] trackpointIds = new long[track.second.length];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
long startTrackPointId = trackpointIds[9];
|
long startTrackPointId = trackpointIds[9];
|
||||||
|
// when
|
||||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
|
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
|
||||||
|
|
||||||
|
// then
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(trackPointIterator.hasNext());
|
Assert.assertTrue(trackPointIterator.hasNext());
|
||||||
TrackPoint trackPoint = trackPointIterator.next();
|
TrackPoint trackPoint = trackPointIterator.next();
|
||||||
@@ -686,33 +949,36 @@ public class CustomContentProviderUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetTrackPointLocationIterator_asc() {
|
public void testGetTrackPointLocationIterator_asc() {
|
||||||
// Insert track, point at first.
|
// given
|
||||||
long trackId = System.currentTimeMillis();
|
long trackId = System.currentTimeMillis();
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
|
|
||||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
long[] trackpointIds = new long[track.second.length];
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second[i], track.first.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
long startTrackPointId = trackpointIds[0];
|
long startTrackPointId = trackpointIds[0];
|
||||||
|
|
||||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
|
// when
|
||||||
|
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
|
||||||
|
|
||||||
|
// then
|
||||||
for (int i = 0; i < trackpointIds.length; i++) {
|
for (int i = 0; i < trackpointIds.length; i++) {
|
||||||
Assert.assertTrue(locationIterator.hasNext());
|
Assert.assertTrue(trackPointIterator.hasNext());
|
||||||
TrackPoint trackPoint = locationIterator.next();
|
TrackPoint trackPoint = trackPointIterator.next();
|
||||||
Assert.assertEquals(startTrackPointId + i, locationIterator.getTrackPointId());
|
Assert.assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
|
||||||
|
|
||||||
checkLocation(i, trackPoint.getLocation());
|
checkLocation(i, trackPoint.getLocation());
|
||||||
}
|
}
|
||||||
Assert.assertFalse(locationIterator.hasNext());
|
Assert.assertFalse(trackPointIterator.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the value of a location.
|
* Checks the value of a location.
|
||||||
*
|
*
|
||||||
* @param i the index of this location which created in the method {@link TestDataUtil#getTrack(long, int)}
|
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(long, int)}
|
||||||
* @param location the location to be checked
|
* @param location the location to be checked
|
||||||
*/
|
*/
|
||||||
private void checkLocation(int i, Location location) {
|
private void checkLocation(int i, Location location) {
|
||||||
@@ -722,15 +988,6 @@ public class CustomContentProviderUtilsTest {
|
|||||||
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a track with locations into the database.
|
|
||||||
*
|
|
||||||
* @param track track to be inserted
|
|
||||||
*/
|
|
||||||
private void insertTrackWithLocations(Track track) {
|
|
||||||
contentProviderUtils.insertTrack(track);
|
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFormatIdListForUri() {
|
public void testFormatIdListForUri() {
|
||||||
|
|||||||
+27
-22
@@ -5,6 +5,7 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
@@ -36,7 +37,8 @@ public class CustomSQLiteOpenHelperTest {
|
|||||||
*
|
*
|
||||||
* @return Map(TableName, SQL)
|
* @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<>();
|
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")) {
|
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) {
|
if (cursor != null) {
|
||||||
@@ -53,6 +55,7 @@ public class CustomSQLiteOpenHelperTest {
|
|||||||
*
|
*
|
||||||
* @return Map(tableName, SQL)
|
* @return Map(tableName, SQL)
|
||||||
*/
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
||||||
HashMap<String, String> indexSQL = new HashMap<>();
|
HashMap<String, String> indexSQL = new HashMap<>();
|
||||||
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
||||||
@@ -104,24 +107,7 @@ public class CustomSQLiteOpenHelperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onUpgrade_FromVersion23() {
|
public void onUpgrade_FromVersion23() {
|
||||||
// Manually create database schema with version 23 (base version)
|
createVersion23();
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open database with SQL upgrade
|
// Open database with SQL upgrade
|
||||||
Map<String, String> tableByUpgrade;
|
Map<String, String> tableByUpgrade;
|
||||||
@@ -130,10 +116,9 @@ public class CustomSQLiteOpenHelperTest {
|
|||||||
tableByUpgrade = getTableSQL(dbUpgraded);
|
tableByUpgrade = getTableSQL(dbUpgraded);
|
||||||
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open database via
|
|
||||||
context.deleteDatabase(DATABASE_NAME);
|
context.deleteDatabase(DATABASE_NAME);
|
||||||
|
|
||||||
|
// Open database via creation script
|
||||||
Map<String, String> tablesByCreate;
|
Map<String, String> tablesByCreate;
|
||||||
Map<String, String> indicesByCreate;
|
Map<String, String> indicesByCreate;
|
||||||
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
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));
|
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
|
||||||
|
|
||||||
// then - verify custom indices
|
// 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(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
|
||||||
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.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
|
// then - verify custom indices
|
||||||
Assert.assertEquals(0, indicesByDowngrade.size());
|
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 {
|
public class ShareContentProviderTest {
|
||||||
|
|
||||||
@Test
|
@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};
|
long[] trackIds = {1, 3, 5};
|
||||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
||||||
|
|
||||||
|
|||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.UintUtils;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class SensorDataCyclingTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
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
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
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
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 2048);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(0, current.getCadence_rpm(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_cadence_sameTime() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, 1024);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 1024);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(current.hasCadence_rpm());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_cadence_rollOverTime() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 1, UintUtils.UINT16_MAX - 1024);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 2, 0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_cadence_rollOverCount() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
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
|
||||||
|
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||||
|
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous, 2000);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(2, current.getSpeed_mps(), 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ import de.dennisguse.opentracks.TrackStubUtils;
|
|||||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||||
import de.dennisguse.opentracks.chart.ChartView;
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,20 +71,13 @@ public class ChartFragmentTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePendingPoint_sensorIncorrect() {
|
public void testCreatePendingPoint_sensorIncorrect() {
|
||||||
|
// given
|
||||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||||
|
|
||||||
// No input.
|
// when
|
||||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
|
||||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
|
||||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
|
||||||
|
|
||||||
// Input incorrect state.
|
// then
|
||||||
// Creates SensorData.
|
|
||||||
SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE);
|
|
||||||
trackPoint.setSensorDataSet(sensorDataSet);
|
|
||||||
// Test.
|
|
||||||
point = chartFragment.createPendingPoint(trackPoint);
|
|
||||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||||
@@ -96,20 +88,16 @@ public class ChartFragmentTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePendingPoint_sensorCorrect() {
|
public void testCreatePendingPoint_sensorCorrect() {
|
||||||
|
// given
|
||||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||||
// No input.
|
trackPoint.setHeartRate_bpm(100f);
|
||||||
|
trackPoint.setCyclingCadence_rpm(101f);
|
||||||
|
trackPoint.setPower(102f);
|
||||||
|
|
||||||
|
// when
|
||||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
|
||||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
|
||||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
|
||||||
|
|
||||||
// Creates SensorData.
|
// then
|
||||||
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
|
|
||||||
|
|
||||||
// Creates SensorDataSet.
|
|
||||||
trackPoint.setSensorDataSet(sensorDataSet);
|
|
||||||
// Test.
|
|
||||||
point = chartFragment.createPendingPoint(trackPoint);
|
|
||||||
Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
|
Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||||
Assert.assertEquals(101.0, point.getCadence(), 0.01);
|
Assert.assertEquals(101.0, point.getCadence(), 0.01);
|
||||||
Assert.assertEquals(102.0, point.getPower(), 0.01);
|
Assert.assertEquals(102.0, point.getPower(), 0.01);
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ public abstract class AbstractTestFileTrackImporter {
|
|||||||
* @param trackPointId the track point id
|
* @param trackPointId the track point id
|
||||||
*/
|
*/
|
||||||
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
|
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(1), eq(trackId))).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+79
-19
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
@@ -16,6 +17,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
@@ -47,21 +49,27 @@ public class ExportImportTest {
|
|||||||
private static final String TRACK_ICON = "the track icon";
|
private static final String TRACK_ICON = "the track icon";
|
||||||
private static final String TRACK_CATEGORY = "the category";
|
private static final String TRACK_CATEGORY = "the category";
|
||||||
private static final String TRACK_DESCRIPTION = "the description";
|
private static final String TRACK_DESCRIPTION = "the description";
|
||||||
|
|
||||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||||
|
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
||||||
|
|
||||||
private long importTrackId;
|
private long importTrackId;
|
||||||
private final long trackId = System.currentTimeMillis();
|
private final long trackId = System.currentTimeMillis();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||||
track.setIcon(TRACK_ICON);
|
track.first.setIcon(TRACK_ICON);
|
||||||
track.setCategory(TRACK_CATEGORY);
|
track.first.setCategory(TRACK_CATEGORY);
|
||||||
track.setDescription(TRACK_DESCRIPTION);
|
track.first.setDescription(TRACK_DESCRIPTION);
|
||||||
contentProviderUtils.insertTrack(track);
|
contentProviderUtils.insertTrack(track.first);
|
||||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
|
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
||||||
|
|
||||||
|
trackPoints.clear();
|
||||||
|
trackPoints.addAll(Arrays.asList(track.second));
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Waypoint waypoint = new Waypoint(track.getTrackPoints().get(i).getLocation());
|
Waypoint waypoint = new Waypoint(track.second[i].getLocation());
|
||||||
waypoint.setName("the waypoint " + i);
|
waypoint.setName("the waypoint " + i);
|
||||||
waypoint.setDescription("the waypoint description " + i);
|
waypoint.setDescription("the waypoint description " + i);
|
||||||
waypoint.setCategory("the waypoint category" + i);
|
waypoint.setCategory("the waypoint category" + i);
|
||||||
@@ -95,16 +103,13 @@ public class ExportImportTest {
|
|||||||
// given
|
// given
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackFileFormat trackFileFormat = TrackFileFormat.KML_WITH_TRACKDETAIL;
|
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track});
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
trackExporter.writeTrack(context, outputStream);
|
trackExporter.writeTrack(context, outputStream);
|
||||||
|
|
||||||
System.out.println(outputStream.toString());
|
|
||||||
|
|
||||||
// 2. import
|
// 2. import
|
||||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
|
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
|
||||||
@@ -114,7 +119,6 @@ public class ExportImportTest {
|
|||||||
// 1. track
|
// 1. track
|
||||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
assertNotNull(importedTrack);
|
assertNotNull(importedTrack);
|
||||||
assertEquals(track.getTrackPoints(), importedTrack.getTrackPoints());
|
|
||||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||||
assertEquals(track.getName(), importedTrack.getName());
|
assertEquals(track.getName(), importedTrack.getName());
|
||||||
@@ -123,14 +127,42 @@ public class ExportImportTest {
|
|||||||
// 2. waypoints
|
// 2. waypoints
|
||||||
assertWaypoints();
|
assertWaypoints();
|
||||||
|
|
||||||
//TODO Check absolute time of trackpoints
|
// 3. trackpoints
|
||||||
|
assertTrackpoints(false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void kml_with_trackdetail_and_sensordata() {
|
public void kml_with_trackdetail_and_sensordata() {
|
||||||
// TODO
|
// given
|
||||||
Log.e(TAG, "Test not implemented.");
|
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
|
@LargeTest
|
||||||
@@ -167,8 +199,7 @@ public class ExportImportTest {
|
|||||||
// given
|
// given
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
TrackFileFormat trackFileFormat = TrackFileFormat.GPX;
|
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}, null);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
// 1. export
|
// 1. export
|
||||||
@@ -184,7 +215,6 @@ public class ExportImportTest {
|
|||||||
// 1. track
|
// 1. track
|
||||||
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
||||||
assertNotNull(trackImported);
|
assertNotNull(trackImported);
|
||||||
assertEquals(track.getTrackPoints(), trackImported.getTrackPoints());
|
|
||||||
assertEquals(track.getCategory(), trackImported.getCategory());
|
assertEquals(track.getCategory(), trackImported.getCategory());
|
||||||
assertEquals(track.getDescription(), trackImported.getDescription());
|
assertEquals(track.getDescription(), trackImported.getDescription());
|
||||||
assertEquals(track.getName(), trackImported.getName());
|
assertEquals(track.getName(), trackImported.getName());
|
||||||
@@ -195,7 +225,8 @@ public class ExportImportTest {
|
|||||||
// 2. waypoints
|
// 2. waypoints
|
||||||
assertWaypoints();
|
assertWaypoints();
|
||||||
|
|
||||||
//TODO Check absolute time of trackpoints
|
// 3. trackpoints
|
||||||
|
assertTrackpoints(false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertWaypoints() {
|
private void assertWaypoints() {
|
||||||
@@ -216,4 +247,33 @@ public class ExportImportTest {
|
|||||||
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+4
-5
@@ -30,7 +30,6 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.anyInt;
|
|
||||||
import static org.mockito.Mockito.anyLong;
|
import static org.mockito.Mockito.anyLong;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
@@ -91,7 +90,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||||
@@ -120,7 +119,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
@@ -152,7 +151,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
@@ -206,7 +205,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||||
|
|
||||||
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), anyInt(), anyLong())).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), anyLong())).thenReturn(1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -75,7 +75,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
|||||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
// A flush happens at the end
|
// A flush happens at the end
|
||||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
|
||||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||||
|
|
||||||
|
|||||||
+2
@@ -21,6 +21,8 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
||||||
*
|
*
|
||||||
|
|||||||
+154
-50
@@ -24,6 +24,7 @@ import android.os.IBinder;
|
|||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
import androidx.test.filters.MediumTest;
|
import androidx.test.filters.MediumTest;
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
@@ -43,6 +44,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
@@ -86,7 +89,7 @@ public class TrackRecordingServiceTest {
|
|||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
sharedPreferences.edit().clear().apply();
|
sharedPreferences.edit().clear().commit();
|
||||||
|
|
||||||
// Ensure that the database is empty before every test
|
// Ensure that the database is empty before every test
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
@@ -121,13 +124,16 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_noTracks() throws Exception {
|
public void testRecording_noTracks() throws Exception {
|
||||||
|
// given
|
||||||
List<Track> tracks = contentProviderUtils.getAllTracks();
|
List<Track> tracks = contentProviderUtils.getAllTracks();
|
||||||
Assert.assertTrue(tracks.isEmpty());
|
Assert.assertTrue(tracks.isEmpty());
|
||||||
|
|
||||||
|
// when
|
||||||
Intent startIntent = createStartIntent(context);
|
Intent startIntent = createStartIntent(context);
|
||||||
mServiceRule.startService(startIntent);
|
mServiceRule.startService(startIntent);
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
||||||
|
|
||||||
|
// then
|
||||||
// Test if we start in no-recording mode by default.
|
// Test if we start in no-recording mode by default.
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||||
@@ -136,19 +142,92 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_oldTracks() throws Exception {
|
public void testRecording_oldTracks() throws Exception {
|
||||||
|
// given
|
||||||
createDummyTrack(trackId, -1L, false);
|
createDummyTrack(trackId, -1L, false);
|
||||||
|
|
||||||
|
// when
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
public void testRecording_serviceRestart_whileRecording() throws Exception {
|
||||||
Intent startIntent = createStartIntent(context);
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(startIntent));
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
|
||||||
|
//when
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertTrue(service.isRecording());
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testRecording_pauseAndResume() throws Exception {
|
||||||
|
// given
|
||||||
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
newTrackPoint(service);
|
||||||
|
|
||||||
|
// when
|
||||||
|
service.pauseCurrentTrack();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||||
|
|
||||||
|
//when
|
||||||
|
service.resumeTrack(trackId);
|
||||||
|
newTrackPoint(service);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertTrue(service.isRecording());
|
||||||
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(5, trackPoints.size());
|
||||||
|
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testRecording_resumeStoppedTrack() throws Exception {
|
||||||
|
// given
|
||||||
|
createDummyTrack(trackId, -1L, true);
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
newTrackPoint(service);
|
||||||
|
service.endCurrentTrack();
|
||||||
|
|
||||||
|
Assert.assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
|
||||||
|
|
||||||
|
// when
|
||||||
|
service.resumeTrack(trackId);
|
||||||
|
newTrackPoint(service);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertTrue(service.isRecording());
|
||||||
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(4, trackPoints.size());
|
||||||
|
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FlakyTest(detail = "Sometimes fails on CI.")
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||||
|
// given
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
|
||||||
|
// when
|
||||||
// Just set recording track to a bogus value.
|
// Just set recording track to a bogus value.
|
||||||
// Make sure that the service will not start recording and will clear the bogus track.
|
// Make sure that the service will not start recording and will clear the bogus track.
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||||
@@ -161,33 +240,86 @@ public class TrackRecordingServiceTest {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||||
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
service.startNewTrack();
|
service.startNewTrack();
|
||||||
Assert.assertTrue(service.isRecording());
|
Assert.assertTrue(service.isRecording());
|
||||||
|
|
||||||
long trackId = service.getRecordingTrackId();
|
long trackId = service.getRecordingTrackId();
|
||||||
|
|
||||||
|
// when
|
||||||
long newTrackId = service.startNewTrack();
|
long newTrackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||||
|
|
||||||
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||||
|
|
||||||
service.endCurrentTrack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||||
|
// given
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
Assert.assertFalse(service.isRecording());
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
// when
|
||||||
// Ending the current track when there is no recording should not result in any error.
|
// Ending the current track when there is no recording should not result in any error.
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
|
// then
|
||||||
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
Assert.assertFalse(PreferencesUtils.isRecording(context));
|
||||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||||
|
// given
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
// when
|
||||||
|
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(-1L, waypointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||||
|
// given
|
||||||
|
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||||
|
service.startNewTrack();
|
||||||
|
Assert.assertTrue(service.isRecording());
|
||||||
|
newTrackPoint(service);
|
||||||
|
long trackId = service.getRecordingTrackId();
|
||||||
|
|
||||||
|
// when
|
||||||
|
long waypointId = service.insertWaypoint(null, null, null, null);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertNotEquals(-1L, waypointId);
|
||||||
|
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
|
||||||
|
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
|
||||||
|
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||||
|
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||||
|
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||||
|
Assert.assertNotNull(wpt.getLocation());
|
||||||
|
|
||||||
|
service.endCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTrack(Track track, boolean isRecording) {
|
||||||
|
Assert.assertTrue(track.getId() >= 0);
|
||||||
|
contentProviderUtils.insertTrack(track);
|
||||||
|
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
||||||
|
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||||
|
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Do not use to create a track that is currently recording.
|
// NOTE: Do not use to create a track that is currently recording.
|
||||||
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||||
Track dummyTrack = new Track();
|
Track dummyTrack = new Track();
|
||||||
@@ -199,58 +331,30 @@ public class TrackRecordingServiceTest {
|
|||||||
addTrack(dummyTrack, isRecording);
|
addTrack(dummyTrack, isRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTrack(Track track, boolean isRecording) {
|
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService) throws InterruptedException {
|
||||||
Assert.assertTrue(track.getId() >= 0);
|
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||||
contentProviderUtils.insertTrack(track);
|
}
|
||||||
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
|
|
||||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a location and waits for 200ms.
|
* Inserts a location and waits for 200ms.
|
||||||
*/
|
*/
|
||||||
private void insertLocation(TrackRecordingServiceInterface trackRecordingService) 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 location = new Location("gps");
|
||||||
location.setLongitude(35.0f);
|
location.setLongitude(longitude);
|
||||||
location.setLatitude(45.0f);
|
location.setLatitude(latitude);
|
||||||
location.setAccuracy(5);
|
location.setAccuracy(accuracy);
|
||||||
location.setSpeed(10);
|
location.setSpeed(speed);
|
||||||
location.setTime(System.currentTimeMillis());
|
location.setTime(time);
|
||||||
location.setBearing(3.0f);
|
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);
|
Thread.sleep(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
Assert.assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
|
||||||
Assert.assertEquals(-1L, waypointId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
|
||||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
|
||||||
service.startNewTrack();
|
|
||||||
Assert.assertTrue(service.isRecording());
|
|
||||||
insertLocation(service);
|
|
||||||
|
|
||||||
long trackId = service.getRecordingTrackId();
|
|
||||||
long waypointId = service.insertWaypoint(null, null, null, null);
|
|
||||||
Assert.assertNotEquals(-1L, waypointId);
|
|
||||||
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
|
|
||||||
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
|
||||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
|
||||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
|
||||||
Assert.assertNotNull(wpt.getLocation());
|
|
||||||
|
|
||||||
service.endCurrentTrack();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.MediumTest;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests insert location.
|
||||||
|
* <p>
|
||||||
|
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
|
||||||
|
* So, if it is executed on a real device, data might be lost.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TrackRecordingServiceTestLocation {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
|
private TrackRecordingServiceInterface service;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws TimeoutException {
|
||||||
|
// Set up the mock content resolver
|
||||||
|
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||||
|
};
|
||||||
|
customContentProvider.attachInfo(context, null);
|
||||||
|
|
||||||
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
|
// Let's use default values.
|
||||||
|
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||||
|
sharedPreferences.edit().clear().commit();
|
||||||
|
|
||||||
|
service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
// Reset service (if some previous test failed)
|
||||||
|
if (service.isRecording() || service.isPaused()) {
|
||||||
|
service.endCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the database is empty after every test
|
||||||
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(6, trackPoints.size());
|
||||||
|
Assert.assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(2, trackPoints.size());
|
||||||
|
Assert.assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @MediumTest
|
||||||
|
// @Test
|
||||||
|
// public void testOnLocationChangedAsync_repeatedTime() throws Exception {
|
||||||
|
// // when
|
||||||
|
// TrackRecordingServiceTest.insertLocation(service, 45.0, 35.0, 5, 15, 5);
|
||||||
|
// TrackRecordingServiceTest.insertLocation(service, 55.0, 35.0, 5, 15, 5);
|
||||||
|
// TrackRecordingServiceTest.insertLocation(service, 65.0, 35.0, 5, 15, 5);
|
||||||
|
//
|
||||||
|
// 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_idle() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(3, trackPoints.size());
|
||||||
|
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(4, trackPoints.size());
|
||||||
|
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
|
||||||
|
Assert.assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SensorDataSet getSensorData() {
|
||||||
|
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
|
sensorDataSet.set(new SensorDataHeartRate("sensorName", "sensorAddress", 5f));
|
||||||
|
return sensorDataSet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(6, trackPoints.size());
|
||||||
|
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_segment() throws Exception {
|
||||||
|
// given
|
||||||
|
long trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
// when
|
||||||
|
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();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||||
|
Assert.assertEquals(7, trackPoints.size());
|
||||||
|
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
|
||||||
|
Assert.assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
|
||||||
|
Assert.assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-1
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
@@ -215,7 +216,9 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
location.setSpeed(10);
|
location.setSpeed(10);
|
||||||
location.setTime(startTime + i * 10000);
|
location.setTime(startTime + i * 10000);
|
||||||
location.setBearing(3.0f);
|
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) {
|
if (i % 7 == 0) {
|
||||||
service.insertWaypoint(null, null, null, null);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ public class AnnouncementUtilsTest {
|
|||||||
stats.setTotalElevationGain(6000);
|
stats.setTotalElevationGain(6000);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane");
|
||||||
|
|
||||||
// then
|
// then
|
||||||
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||||
|
|
||||||
|
public class BluetoothUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseHeartRate_uint8() {
|
||||||
|
// given
|
||||||
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
||||||
|
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||||
|
|
||||||
|
// when
|
||||||
|
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(60, heartRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseHeartRate_uint16() {
|
||||||
|
// given
|
||||||
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.HEART_RATE_SERVICE_UUID, 0, 0);
|
||||||
|
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||||
|
|
||||||
|
// when
|
||||||
|
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(257, heartRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseCyclingSpeedCadence_crankOnly() {
|
||||||
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||||
|
characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||||
|
|
||||||
|
// when
|
||||||
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||||
|
Assert.assertNull(sensor.getSpeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||||
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||||
|
characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
|
// when
|
||||||
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertNull(sensor.getCadence());
|
||||||
|
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseCyclingSpeedCadence_crankWheel() {
|
||||||
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||||
|
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
|
// when
|
||||||
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||||
|
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
@Test
|
||||||
public void testGetTime_timezone() {
|
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: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);
|
||||||
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,52 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
|
public class TrackPointUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fixTime_none() {
|
||||||
|
// given
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
|
trackPoint.setTime(time);
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackPointUtils.fixTime(trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(time, trackPoint.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fixTime_0() {
|
||||||
|
// given
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
|
trackPoint.setTime(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackPointUtils.fixTime(trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(time, trackPoint.getTime(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fixTime_gpsWeekRollover() {
|
||||||
|
// given
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
|
trackPoint.setTime(time - 1024 * UnitConversions.ONE_WEEK_MS);
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackPointUtils.fixTime(trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(time, trackPoint.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
<!-- 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
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@@ -26,36 +56,69 @@ limitations under the License.
|
|||||||
android:label="${applicationName}"
|
android:label="${applicationName}"
|
||||||
android:theme="@style/ThemeCustom">
|
android:theme="@style/ThemeCustom">
|
||||||
|
|
||||||
<!-- Activities -->
|
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".TrackDeleteActivity"
|
android:name=".TrackDeleteActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent" />
|
android:theme="@style/TrackDeleteActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".HelpActivity"
|
android:name=".HelpActivity"
|
||||||
android:label="@string/menu_help" />
|
android:label="@string/menu_help" />
|
||||||
|
|
||||||
|
<activity android:name=".io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.importer.ImportActivity"
|
android:name=".io.file.importer.ImportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent">
|
android:theme="@style/ThemeCustomTransparentActivity">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.ATTACH_DATA" />
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="application/gpx" />
|
<data
|
||||||
<data android:mimeType="application/gpx+xml" />
|
android:host="*"
|
||||||
<data android:mimeType="application/vnd.google-earth.gpx" />
|
android:mimeType="application/vnd.google-earth.kmz" />
|
||||||
<data android:mimeType="application/vnd.google-earth.gpx+xml" />
|
<data
|
||||||
<data android:scheme="file" />
|
android:host="*"
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathPattern="*kmz" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathPattern="*kml" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="application/gpx+xml" />
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathPattern="*gpx" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</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" />
|
<activity android:name=".MarkerEditActivity" />
|
||||||
|
|
||||||
@@ -78,9 +141,9 @@ limitations under the License.
|
|||||||
android:resource="@xml/searchable" />
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity android:name=".TrackRecordedActivity" />
|
||||||
android:name=".TrackDetailActivity"
|
|
||||||
android:screenOrientation="userPortrait" />
|
<activity android:name=".TrackRecordingActivity" />
|
||||||
|
|
||||||
<activity android:name=".TrackEditActivity" />
|
<activity android:name=".TrackEditActivity" />
|
||||||
|
|
||||||
@@ -94,12 +157,29 @@ limitations under the License.
|
|||||||
android:value=".SearchListActivity" />
|
android:value=".SearchListActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".AggregatedStatisticsActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".io.file.exporter.ExportActivity"
|
android:name=".io.file.exporter.ExportActivity"
|
||||||
android:theme="@style/ThemeCustomTransparent" />
|
android:theme="@style/ThemeCustomTransparentActivity" />
|
||||||
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
<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
|
<provider
|
||||||
android:name=".content.provider.ShareContentProvider"
|
android:name=".content.provider.ShareContentProvider"
|
||||||
android:authorities="${applicationId}.content"
|
android:authorities="${applicationId}.content"
|
||||||
@@ -124,32 +204,4 @@ limitations under the License.
|
|||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/recording_service" />
|
android:label="@string/recording_service" />
|
||||||
</application>
|
</application>
|
||||||
<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>
|
</manifest>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.ViewUtils;
|
||||||
|
|
||||||
public class AboutActivity extends AbstractActivity {
|
public class AboutActivity extends AbstractActivity {
|
||||||
|
|
||||||
@@ -27,6 +29,8 @@ public class AboutActivity extends AbstractActivity {
|
|||||||
|
|
||||||
TextView textURL = findViewById(R.id.about_app_url);
|
TextView textURL = findViewById(R.id.about_app_url);
|
||||||
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_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() {
|
protected int getLayoutResId() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract class for the following common tasks across
|
* An abstract class for the following common tasks across
|
||||||
* {@link TrackListActivity}, {@link TrackDetailActivity}, and
|
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and
|
||||||
* {@link SearchListActivity}:
|
* {@link SearchListActivity}:
|
||||||
* <p>
|
* <p>
|
||||||
* - share track <br>
|
* - share track <br>
|
||||||
@@ -62,23 +62,22 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
@Override
|
@Override
|
||||||
public void onConfirmDeleteDone(long[] trackIds) {
|
public void onConfirmDeleteDone(long[] trackIds) {
|
||||||
boolean stopRecording = false;
|
boolean stopRecording = false;
|
||||||
if (trackIds.length == 1 && trackIds[0] == -1L) {
|
|
||||||
stopRecording = true;
|
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||||
} else {
|
for (long trackId : trackIds) {
|
||||||
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
if (trackId == recordingTrackId) {
|
||||||
for (long trackId : trackIds) {
|
stopRecording = true;
|
||||||
if (trackId == recordingTrackId) {
|
break;
|
||||||
stopRecording = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopRecording) {
|
if (stopRecording) {
|
||||||
getTrackRecordingServiceConnection().stopRecording(this, false);
|
getTrackRecordingServiceConnection().stopRecording(this, false);
|
||||||
}
|
}
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
|
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
|
||||||
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
|
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
|
||||||
startActivityForResult(intent, DELETE_REQUEST_CODE);
|
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;
|
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 {
|
public class HelpActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getLayoutResId() {
|
protected int getLayoutResId() {
|
||||||
return R.layout.help;
|
return R.layout.help;
|
||||||
|
|||||||
@@ -109,12 +109,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleteMarkerDone() {
|
public void onDeleteMarkerDone() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(this::finish);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,7 +120,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
|||||||
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
|
||||||
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
|
||||||
super(fragmentManager);
|
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import android.util.Pair;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
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_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_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 CAMERA_REQUEST_CODE = 5;
|
||||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||||
|
|
||||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||||
private long trackId;
|
private long trackId;
|
||||||
private long markerId;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
|
||||||
private Waypoint waypoint;
|
private Waypoint waypoint;
|
||||||
|
|
||||||
private MenuItem insertPhotoMenuItem;
|
private MenuItem insertPhotoMenuItem;
|
||||||
@@ -80,15 +80,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
private EditText waypointDescription;
|
private EditText waypointDescription;
|
||||||
private ImageView waypointPhoto;
|
private ImageView waypointPhoto;
|
||||||
private ImageView waypointDeletePhotoBtn;
|
private ImageView waypointDeletePhotoBtn;
|
||||||
private Button done;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
|
||||||
|
|
||||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||||
|
|
||||||
@@ -101,26 +99,64 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
||||||
|
|
||||||
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
||||||
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
|
waypointDeletePhotoBtn.setOnClickListener(v -> {
|
||||||
@Override
|
if (waypoint != null && waypoint.hasPhoto()) {
|
||||||
public void onClick(View v) {
|
waypoint.setPhotoUrl(null);
|
||||||
if (waypoint != null && waypoint.hasPhoto())
|
|
||||||
waypoint.setPhotoUrl(null);
|
|
||||||
waypointPhoto.setImageBitmap(null);
|
|
||||||
photoUri = null;
|
|
||||||
hideAndShowOptions();
|
|
||||||
}
|
}
|
||||||
|
waypointPhoto.setImageBitmap(null);
|
||||||
|
photoUri = null;
|
||||||
|
hideAndShowOptions();
|
||||||
});
|
});
|
||||||
|
|
||||||
Button cancel = findViewById(R.id.marker_edit_cancel);
|
Button cancel = findViewById(R.id.marker_edit_cancel);
|
||||||
cancel.setOnClickListener(new OnClickListener() {
|
cancel.setOnClickListener(v -> finish());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
final boolean isNewMarker = markerId == -1L;
|
||||||
finish();
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (photoUri != null) {
|
||||||
|
setWaypointImageView(photoUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAndShowOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -129,6 +165,12 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
trackRecordingServiceConnection.startConnection(this);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putParcelable(BUNDLE_PHOTO_URI, photoUri);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
@@ -198,54 +240,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
super.onActivityResult(requestCode, resultCode, data);
|
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.
|
* Checks and hide/shows all buttons/options about marker photo options.
|
||||||
*
|
*
|
||||||
@@ -272,13 +266,13 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
*
|
*
|
||||||
* @param uri the uri photo.
|
* @param uri the uri photo.
|
||||||
*/
|
*/
|
||||||
private void setWaypointImageView(Uri uri) {
|
private void setWaypointImageView(@NonNull Uri uri) {
|
||||||
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
||||||
FileDescriptor fd = pfd.getFileDescriptor();
|
FileDescriptor fd = pfd.getFileDescriptor();
|
||||||
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
||||||
waypointPhoto.setImageBitmap(bitmap);
|
waypointPhoto.setImageBitmap(bitmap);
|
||||||
hideAndShowOptions();
|
hideAndShowOptions();
|
||||||
} catch(IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, e.getMessage());
|
Log.e(TAG, e.getMessage());
|
||||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
@@ -295,9 +289,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a marker.
|
|
||||||
*/
|
|
||||||
private void addMarker() {
|
private void addMarker() {
|
||||||
trackRecordingServiceConnection.addMarker(this,
|
trackRecordingServiceConnection.addMarker(this,
|
||||||
waypointName.getText().toString(),
|
waypointName.getText().toString(),
|
||||||
@@ -306,15 +297,12 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
photoUri != null ? photoUri.toString() : null);
|
photoUri != null ? photoUri.toString() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves a marker.
|
|
||||||
*/
|
|
||||||
private void saveMarker() {
|
private void saveMarker() {
|
||||||
waypoint.setName(waypointName.getText().toString());
|
waypoint.setName(waypointName.getText().toString());
|
||||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||||
waypoint.setDescription(waypointDescription.getText().toString());
|
waypoint.setDescription(waypointDescription.getText().toString());
|
||||||
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||||
|
|
||||||
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
new ContentProviderUtils(this).updateWaypoint(this, waypoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -82,12 +80,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
MarkerListActivity.this.invalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -134,13 +127,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
listView = findViewById(R.id.marker_list);
|
listView = findViewById(R.id.marker_list);
|
||||||
listView.setEmptyView(findViewById(R.id.marker_list_empty));
|
listView.setEmptyView(findViewById(R.id.marker_list_empty));
|
||||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
@Override
|
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
||||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
startActivity(intent);
|
||||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||||
@Override
|
@Override
|
||||||
@@ -277,8 +267,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_select_all:
|
case R.id.list_context_menu_select_all:
|
||||||
int size = listView.getCount();
|
for (int i = 0; i < listView.getCount(); i++) {
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
listView.setItemChecked(i, true);
|
listView.setItemChecked(i, true);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import android.view.KeyEvent;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
@@ -153,12 +151,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(SearchListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> arrayAdapter.notifyDataSetChanged());
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
arrayAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -208,20 +201,17 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
ListView listView = findViewById(R.id.search_list);
|
ListView listView = findViewById(R.id.search_list);
|
||||||
listView.setAdapter(arrayAdapter);
|
listView.setAdapter(arrayAdapter);
|
||||||
listView.setEmptyView(findViewById(R.id.search_list_empty));
|
listView.setEmptyView(findViewById(R.id.search_list_empty));
|
||||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
@Override
|
Map<String, Object> item = arrayAdapter.getItem(position);
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
||||||
Map<String, Object> item = arrayAdapter.getItem(position);
|
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
||||||
Long trackId = (Long) item.get(TRACK_ID_FIELD);
|
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
|
||||||
Long markerId = (Long) item.get(MARKER_ID_FIELD);
|
if (markerId != null) {
|
||||||
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackDetailActivity.class);
|
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
|
||||||
if (markerId != null) {
|
} else {
|
||||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
|
intent = intent.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
} else {
|
|
||||||
intent = intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
|
||||||
}
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
startActivity(intent);
|
||||||
});
|
});
|
||||||
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
@@ -346,12 +336,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
|
||||||
|
|
||||||
// Use the UI thread to display the results
|
// Use the UI thread to display the results
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
arrayAdapter.clear();
|
||||||
public void run() {
|
arrayAdapter.addAll(displayResults);
|
||||||
arrayAdapter.clear();
|
|
||||||
arrayAdapter.addAll(displayResults);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,12 +435,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleteMarkerDone() {
|
public void onDeleteMarkerDone() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> handleIntent(getIntent()));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
handleIntent(getIntent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -463,11 +445,6 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDeleted() {
|
protected void onDeleted() {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> handleIntent(getIntent()));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
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.os.StrictMode;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +27,10 @@ public class Startup extends Application {
|
|||||||
|
|
||||||
//Set default values of preferences on first start.
|
//Set default values of preferences on first start.
|
||||||
PreferencesUtils.resetPreferences(this, false);
|
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.
|
//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 boolean isPaused;
|
||||||
private long totalTime = 0;
|
private long totalTime = 0;
|
||||||
|
|
||||||
// the timestamp for the toal time
|
|
||||||
private long totalTimeTimestamp = 0;
|
private long totalTimeTimestamp = 0;
|
||||||
|
|
||||||
private boolean isResumed = false;
|
private boolean isResumed = false;
|
||||||
|
|||||||
@@ -16,26 +16,26 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
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
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class TrackDeleteActivity extends Activity {
|
public class TrackDeleteActivity extends AbstractActivity {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
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
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -44,53 +44,48 @@ public class TrackDeleteActivity extends Activity {
|
|||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
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();
|
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
|
||||||
if (retained instanceof DeleteAsyncTask) {
|
|
||||||
deleteAsyncTask = (DeleteAsyncTask) retained;
|
for (long id : trackIds) {
|
||||||
deleteAsyncTask.setActivity(this);
|
if (Thread.interrupted()) {
|
||||||
} else {
|
break;
|
||||||
deleteAsyncTask = new DeleteAsyncTask(this, trackIds);
|
}
|
||||||
deleteAsyncTask.execute();
|
contentProviderUtils.deleteTrack(TrackDeleteActivity.this, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnUiThread(TrackDeleteActivity.this::onAsyncTaskCompleted);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object onRetainNonConfigurationInstance() {
|
protected void onStart() {
|
||||||
deleteAsyncTask.setActivity(null);
|
super.onStart();
|
||||||
return deleteAsyncTask;
|
deleteThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Dialog onCreateDialog(int id) {
|
protected void onStop() {
|
||||||
if (id != DIALOG_PROGRESS_ID) {
|
super.onStop();
|
||||||
return null;
|
deleteThread.interrupt();
|
||||||
}
|
}
|
||||||
return DialogUtils.createSpinnerProgressDialog(
|
|
||||||
this, R.string.track_delete_progress_message, new DialogInterface.OnCancelListener() {
|
@Override
|
||||||
@Override
|
protected int getLayoutResId() {
|
||||||
public void onCancel(DialogInterface dialog) {
|
return R.layout.track_delete;
|
||||||
deleteAsyncTask.cancel(true);
|
|
||||||
dialog.dismiss();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes when the associated AsyncTask completes.
|
|
||||||
*/
|
|
||||||
public void onAsyncTaskCompleted() {
|
public void onAsyncTaskCompleted() {
|
||||||
removeDialog(DIALOG_PROGRESS_ID);
|
findViewById(R.id.progressbar).setVisibility(View.INVISIBLE);
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
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.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
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);
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
activityType.setAdapter(adapter);
|
activityType.setAdapter(adapter);
|
||||||
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
activityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||||
@Override
|
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position))));
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
activityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (!hasFocus) {
|
||||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||||
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position)));
|
TrackEditActivity.this, activityType.getText().toString()));
|
||||||
}
|
|
||||||
});
|
|
||||||
activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
|
||||||
if (!hasFocus) {
|
|
||||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
|
||||||
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 = findViewById(R.id.track_edit_activity_type_icon);
|
||||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
|
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
|
||||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
activityTypeIcon.setOnTouchListener((v, event) -> {
|
||||||
@Override
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
|
activityTypeIcon.setOnKeyListener((v, keyCode, event) -> {
|
||||||
@Override
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
|
||||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), activityType.getText().toString());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
description = findViewById(R.id.track_edit_description);
|
description = findViewById(R.id.track_edit_description);
|
||||||
description.setText(track.getDescription());
|
description.setText(track.getDescription());
|
||||||
|
|
||||||
Button saveButton = findViewById(R.id.track_edit_save);
|
Button saveButton = findViewById(R.id.track_edit_save);
|
||||||
saveButton.setOnClickListener(new View.OnClickListener() {
|
saveButton.setOnClickListener(v -> {
|
||||||
@Override
|
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
||||||
public void onClick(View v) {
|
activityType.getText().toString(), description.getText().toString(),
|
||||||
TrackUtils.updateTrack(TrackEditActivity.this, track, nameEditText.getText().toString(),
|
contentProviderUtils);
|
||||||
activityType.getText().toString(), description.getText().toString(),
|
finish();
|
||||||
contentProviderUtils);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Button cancel = findViewById(R.id.track_edit_cancel);
|
Button cancel = findViewById(R.id.track_edit_cancel);
|
||||||
@@ -155,12 +137,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
cancel.setVisibility(View.GONE);
|
cancel.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
setTitle(R.string.menu_edit);
|
setTitle(R.string.menu_edit);
|
||||||
cancel.setOnClickListener(new View.OnClickListener() {
|
cancel.setOnClickListener(v -> finish());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cancel.setVisibility(View.VISIBLE);
|
cancel.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,11 @@ import android.location.LocationManager;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -48,8 +45,6 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
|
|||||||
import androidx.loader.content.CursorLoader;
|
import androidx.loader.content.CursorLoader;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
@@ -57,6 +52,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
@@ -144,14 +140,11 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
TrackListActivity.this.invalidateOptionsMenu();
|
||||||
public void run() {
|
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
||||||
TrackListActivity.this.invalidateOptionsMenu();
|
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
||||||
LoaderManager.getInstance(TrackListActivity.this).restartLoader(0, null, loaderCallbacks);
|
trackController.update(isRecording, recordingTrackPaused);
|
||||||
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
|
|
||||||
trackController.update(isRecording, recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,15 +168,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
/*
|
// After binding changes (e.g., becomes available), update the total time in trackController.
|
||||||
* After binding changes (e.g., becomes available), update the total time in trackController.
|
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
|
||||||
*/
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!startGps) {
|
if (!startGps) {
|
||||||
return;
|
return;
|
||||||
@@ -206,7 +192,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateMenuItems(false, true);
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class);
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
} else if (recordingTrackPaused) {
|
} else if (recordingTrackPaused) {
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
@@ -239,23 +225,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Show trackController when search dialog is dismissed
|
// Show trackController when search dialog is dismissed
|
||||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
||||||
if (searchManager != null) {
|
if (searchManager != null) {
|
||||||
searchManager.setOnDismissListener(new SearchManager.OnDismissListener() {
|
searchManager.setOnDismissListener(() -> trackController.show());
|
||||||
@Override
|
|
||||||
public void onDismiss() {
|
|
||||||
trackController.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listView = findViewById(R.id.track_list);
|
listView = findViewById(R.id.track_list);
|
||||||
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
|
||||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
listView.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||||
@Override
|
Intent newIntent;
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
if (trackId == recordingTrackId) {
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
// Is recording -> open record activity.
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
|
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||||
startActivity(newIntent);
|
.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) {
|
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);
|
ActivityUtils.configureListViewContextualMenu(listView, contextualActionModeCallback);
|
||||||
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).initLoader(0, null, loaderCallbacks);
|
||||||
showStartupDialogs();
|
|
||||||
|
requestGPSPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -352,7 +339,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||||
|
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -382,11 +368,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
} else {
|
} else {
|
||||||
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
|
||||||
|
|
||||||
// Show toast
|
|
||||||
Toast toast = Toast.makeText(this, startGps ? R.string.gps_starting : R.string.gps_stopping, Toast.LENGTH_SHORT);
|
|
||||||
toast.setGravity(Gravity.CENTER, 0, 0);
|
|
||||||
toast.show();
|
|
||||||
|
|
||||||
// Invoke trackRecordingService
|
// Invoke trackRecordingService
|
||||||
if (startGps) {
|
if (startGps) {
|
||||||
trackRecordingServiceConnection.startAndBind(this);
|
trackRecordingServiceConnection.startAndBind(this);
|
||||||
@@ -403,6 +384,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
return true;
|
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:
|
case R.id.track_list_markers:
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -440,19 +425,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Do nothing
|
// 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() {
|
private void requestGPSPermissions() {
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
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);
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE);
|
||||||
@@ -486,7 +458,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
Intent intent;
|
Intent intent;
|
||||||
switch (itemId) {
|
switch (itemId) {
|
||||||
case R.id.list_context_menu_show_on_map:
|
case R.id.list_context_menu_show_on_map:
|
||||||
IntentUtils.showTrackOnMap(this, trackIds);
|
IntentDashboardUtils.startDashboard(this, trackIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_share:
|
case R.id.list_context_menu_share:
|
||||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||||
@@ -499,9 +471,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_delete:
|
case R.id.list_context_menu_delete:
|
||||||
if (trackIds.length > 1 && trackIds.length == listView.getCount()) {
|
|
||||||
trackIds = new long[]{-1L};
|
|
||||||
}
|
|
||||||
deleteTracks(trackIds);
|
deleteTracks(trackIds);
|
||||||
return true;
|
return true;
|
||||||
case R.id.list_context_menu_select_all:
|
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, trackId);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
-208
@@ -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;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -27,10 +11,10 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
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.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
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.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
@@ -58,12 +41,13 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @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_TRACK_ID = "track_id";
|
||||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
|
||||||
|
|
||||||
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";
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
|
|
||||||
@@ -75,47 +59,34 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private ViewPager pager;
|
private ViewPager pager;
|
||||||
private TrackController trackController;
|
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 long trackId;
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
|
|
||||||
private boolean recordingTrackPaused;
|
private boolean recordingTrackPaused;
|
||||||
|
|
||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (is available), update the total time in trackController.
|
// After binding changes (is available), update the total time in trackController.
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> trackController.update(true, recordingTrackPaused));
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
|
if (service == null) {
|
||||||
|
Log.d(TAG, "could not get TrackRecordingService");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!service.isRecording()) {
|
||||||
// Starts or resumes a track.
|
// Starts or resumes a track.
|
||||||
int msg;
|
|
||||||
if (trackId == -1L) {
|
if (trackId == -1L) {
|
||||||
// trackId isn't initialized -> leads a new recording.
|
// trackId isn't initialized -> leads a new recording.
|
||||||
trackId = service.startNewTrack();
|
trackId = service.startNewTrack();
|
||||||
recordingTrackId = trackId;
|
|
||||||
msg = R.string.track_detail_record_success;
|
|
||||||
} else {
|
} else {
|
||||||
// trackId is initialized -> resumes the track.
|
// trackId is initialized -> resumes the track.
|
||||||
recordingTrackId = trackId;
|
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
msg = R.string.track_detail_resume_success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A recording track is on.
|
// A recording track is on.
|
||||||
Toast.makeText(TrackDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
|
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackController.update(true, false);
|
trackController.update(true, false);
|
||||||
trackController.onResume(true, recordingTrackPaused);
|
trackController.onResume(true, recordingTrackPaused);
|
||||||
@@ -126,43 +97,31 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
|
||||||
setLockscreenPolicy();
|
|
||||||
setScreenOnPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
|
||||||
setLockscreenPolicy();
|
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();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
TrackRecordingActivity.this.invalidateOptionsMenu();
|
||||||
public void run() {
|
trackController.update(true, recordingTrackPaused);
|
||||||
TrackDetailActivity.this.invalidateOptionsMenu();
|
|
||||||
boolean isRecording = isRecording();
|
|
||||||
trackController.update(isRecording, recordingTrackPaused);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private MenuItem insertMarkerMenuItem;
|
private MenuItem insertMarkerMenuItem;
|
||||||
private MenuItem markerListMenuItem;
|
private MenuItem markerListMenuItem;
|
||||||
private MenuItem shareMenuItem;
|
|
||||||
private MenuItem resumeMenuItem;
|
|
||||||
|
|
||||||
private final OnClickListener recordListener = new OnClickListener() {
|
private final OnClickListener recordListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -184,58 +143,40 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
private final OnClickListener stopListener = new OnClickListener() {
|
private final OnClickListener stopListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
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);
|
updateMenuItems(true);
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
|
||||||
|
|
||||||
contentProviderUtils = new ContentProviderUtils(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);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
trackDataHub = new TrackDataHub(this);
|
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 = 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);
|
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
|
||||||
tabs.setupWithViewPager(pager);
|
tabs.setupWithViewPager(pager);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
@@ -253,7 +194,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setLockscreenPolicy() {
|
private void setLockscreenPolicy() {
|
||||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this)
|
||||||
&& PreferencesUtils.isRecording(this);
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
@@ -266,7 +207,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setScreenOnPolicy() {
|
private void setScreenOnPolicy() {
|
||||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this)
|
||||||
&& PreferencesUtils.isRecording(this);
|
&& PreferencesUtils.isRecording(this);
|
||||||
|
|
||||||
if (keepScreenOn) {
|
if (keepScreenOn) {
|
||||||
@@ -296,10 +237,23 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
|
|
||||||
if (trackId != -1L) {
|
if (trackId != -1L) {
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
trackController.onResume(true, recordingTrackPaused);
|
||||||
} else {
|
|
||||||
startRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
@Override
|
||||||
@@ -316,32 +270,17 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
trackDataHub.stop();
|
trackDataHub.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getLayoutResId() {
|
protected int getLayoutResId() {
|
||||||
return R.layout.track_detail;
|
return R.layout.track_record;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
setIntent(intent);
|
|
||||||
handleIntent(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
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);
|
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);
|
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||||
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -362,13 +301,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
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:
|
case R.id.track_detail_menu_show_on_map:
|
||||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
IntentDashboardUtils.startDashboard(this, trackId);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_markers:
|
case R.id.track_detail_markers:
|
||||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||||
@@ -380,12 +314,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_detail_delete:
|
|
||||||
deleteTracks(new long[]{trackId});
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_resume_track:
|
|
||||||
startRecording();
|
|
||||||
return true;
|
|
||||||
case R.id.track_detail_settings:
|
case R.id.track_detail_settings:
|
||||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@@ -395,80 +323,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}.
|
* Gets the {@link TrackDataHub}.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public TrackDataHub getTrackDataHub() {
|
public TrackDataHub getTrackDataHub() {
|
||||||
return trackDataHub;
|
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) {
|
|
||||||
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.
|
* Updates the menu items.
|
||||||
*/
|
*/
|
||||||
private void updateMenuItems(boolean isPaused) {
|
private void updateMenuItems(boolean isPaused) {
|
||||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
insertMarkerMenuItem.setVisible(!isPaused);
|
||||||
shareMenuItem.setVisible(!isRecording());
|
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void chooseActivityType(String category) {
|
public void chooseActivityType(String category) {
|
||||||
@@ -482,18 +351,44 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRecording() {
|
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||||
return trackId == recordingTrackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startRecording() {
|
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
|
||||||
trackRecordingServiceConnection.startAndBind(this);
|
super(fm, behavior);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
@Override
|
||||||
* If the binding has happened, then invoke the callback to start a new recording.
|
public int getCount() {
|
||||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
return 3;
|
||||||
* But when the binding occurs, the callback will get invoked.
|
}
|
||||||
*/
|
|
||||||
bindChangedCallback.run();
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package de.dennisguse.opentracks.chart;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
@@ -25,7 +25,7 @@ public class ChartPoint {
|
|||||||
this.elevation = elevation;
|
this.elevation = elevation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, SensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) {
|
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, TrackPoint trackPoint, boolean chartByDistance, boolean metricUnits) {
|
||||||
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
||||||
|
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
@@ -48,15 +48,15 @@ public class ChartPoint {
|
|||||||
speed *= UnitConversions.KM_TO_MI;
|
speed *= UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||||
if (sensorDataSet != null) {
|
if (trackPoint != null) {
|
||||||
if (sensorDataSet.hasHeartRate()) {
|
if (trackPoint.hasHeartRate()) {
|
||||||
heartRate = sensorDataSet.getHeartRate();
|
heartRate = trackPoint.getHeartRate_bpm();
|
||||||
}
|
}
|
||||||
if (sensorDataSet.hasCadence()) {
|
if (trackPoint.hasCyclingCadence()) {
|
||||||
cadence = sensorDataSet.getCadence();
|
cadence = trackPoint.getCyclingCadence_rpm();
|
||||||
}
|
}
|
||||||
if (sensorDataSet.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
power = sensorDataSet.getPower();
|
power = trackPoint.getPower();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,10 +126,14 @@ public class DescriptionGenerator {
|
|||||||
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
writePace(stats.getMaxSpeed(), builder, R.string.description_fastest_pace_in_minute, lineBreak);
|
||||||
|
|
||||||
// Max elevation
|
// Max elevation
|
||||||
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
|
if (stats.hasElevationMax()) {
|
||||||
|
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
// Min elevation
|
// Min elevation
|
||||||
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
if (stats.hasElevationMin()) {
|
||||||
|
writeElevation(stats.getMinElevation(), builder, R.string.description_min_elevation, lineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
// Elevation gain
|
// Elevation gain
|
||||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||||
|
|||||||
@@ -108,28 +108,25 @@ public class SearchEngine {
|
|||||||
/**
|
/**
|
||||||
* Comparator for scored results.
|
* Comparator for scored results.
|
||||||
*/
|
*/
|
||||||
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = new Comparator<ScoredResult>() {
|
private static final Comparator<ScoredResult> SCORED_RESULT_COMPARATOR = (r1, r2) -> {
|
||||||
@Override
|
// Score ordering.
|
||||||
public int compare(ScoredResult r1, ScoredResult r2) {
|
int scoreDiff = Double.compare(r2.score, r1.score);
|
||||||
// Score ordering.
|
if (scoreDiff != 0) {
|
||||||
int scoreDiff = Double.compare(r2.score, r1.score);
|
return scoreDiff;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
private final ContentProviderUtils providerUtils;
|
||||||
|
|||||||
@@ -144,12 +144,9 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
|
|
||||||
PreferencesUtils.register(context, this);
|
PreferencesUtils.register(context, this);
|
||||||
onSharedPreferenceChanged(null, null);
|
onSharedPreferenceChanged(null, null);
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (started) {
|
||||||
public void run() {
|
loadDataForAll();
|
||||||
if (started) {
|
|
||||||
loadDataForAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -178,16 +175,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadTrack(final long trackId) {
|
public void loadTrack(final long trackId) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (trackId == selectedTrackId) {
|
||||||
public void run() {
|
Log.i(TAG, "Not reloading track " + trackId);
|
||||||
if (trackId == selectedTrackId) {
|
return;
|
||||||
Log.i(TAG, "Not reloading track " + trackId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedTrackId = trackId;
|
|
||||||
loadDataForAll();
|
|
||||||
}
|
}
|
||||||
|
selectedTrackId = trackId;
|
||||||
|
loadDataForAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,13 +191,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
* @param trackDataListener the track data listener
|
* @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) {
|
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||||
public void run() {
|
if (started) {
|
||||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
loadDataForListener(trackDataListener);
|
||||||
if (started) {
|
|
||||||
loadDataForListener(trackDataListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,12 +205,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
* @param trackDataListener the track data listener
|
* @param trackDataListener the track data listener
|
||||||
*/
|
*/
|
||||||
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
trackDataManager.unregisterTrackDataListener(trackDataListener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,15 +224,12 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||||
runInHandlerThread(new Runnable() {
|
runInHandlerThread(() -> {
|
||||||
@Override
|
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||||
public void run() {
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||||
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);
|
||||||
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) {
|
if (isOnlyListener) {
|
||||||
resetSamplingState();
|
resetSamplingState();
|
||||||
}
|
}
|
||||||
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.<TrackDataListener>emptySet();
|
Set<TrackDataListener> sampledOutListeners = hasSampledOut ? trackDataListeners : Collections.emptySet();
|
||||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.content.data;
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import java.util.UUID;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
|
||||||
@@ -32,6 +29,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
public class Track {
|
public class Track {
|
||||||
|
|
||||||
private long id = -1L;
|
private long id = -1L;
|
||||||
|
private UUID uuid = UUID.randomUUID();
|
||||||
|
|
||||||
private String name = "";
|
private String name = "";
|
||||||
private String description = "";
|
private String description = "";
|
||||||
private String category = "";
|
private String category = "";
|
||||||
@@ -40,9 +39,6 @@ public class Track {
|
|||||||
|
|
||||||
private TrackStatistics trackStatistics = new TrackStatistics();
|
private TrackStatistics trackStatistics = new TrackStatistics();
|
||||||
|
|
||||||
// Location points (which may not have been loaded)
|
|
||||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
|
||||||
|
|
||||||
public Track() {
|
public Track() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +50,14 @@ public class Track {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UUID getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUuid(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -93,17 +97,4 @@ public class Track {
|
|||||||
public void setTrackStatistics(TrackStatistics trackStatistics) {
|
public void setTrackStatistics(TrackStatistics trackStatistics) {
|
||||||
this.trackStatistics = trackStatistics;
|
this.trackStatistics = trackStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public void addTrackPoint(TrackPoint location) {
|
|
||||||
trackPoints.add(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TrackPoint> getTrackPoints() {
|
|
||||||
return trackPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTrackPoints(ArrayList<TrackPoint> trackPoints) {
|
|
||||||
this.trackPoints = trackPoints;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import android.location.LocationManager;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class extends the standard Android location with extra information.
|
* This class extends the standard Android location with extra information.
|
||||||
*
|
*
|
||||||
@@ -31,6 +29,10 @@ public class TrackPoint {
|
|||||||
|
|
||||||
private final Location location;
|
private final Location location;
|
||||||
|
|
||||||
|
private Float heartRate_bpm = null;
|
||||||
|
private Float cyclingCadence_rpm = null;
|
||||||
|
private Float power = null;
|
||||||
|
|
||||||
public TrackPoint() {
|
public TrackPoint() {
|
||||||
this.location = new Location("");
|
this.location = new Location("");
|
||||||
}
|
}
|
||||||
@@ -39,17 +41,13 @@ public class TrackPoint {
|
|||||||
this.location = location;
|
this.location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackPoint(@NonNull Location location, SensorDataSet sensorDataSet) {
|
public TrackPoint(@NonNull TrackPoint trackPoint) {
|
||||||
this.location = location;
|
|
||||||
this.sensorDataSet = sensorDataSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrackPoint(@NonNull TrackPoint trackPoint, SensorDataSet sensorDataSet) {
|
|
||||||
this.location = trackPoint.getLocation();
|
this.location = trackPoint.getLocation();
|
||||||
this.sensorDataSet = sensorDataSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SensorDataSet sensorDataSet = null;
|
this.heartRate_bpm = trackPoint.getHeartRate_bpm();
|
||||||
|
this.cyclingCadence_rpm = trackPoint.getCyclingCadence_rpm();
|
||||||
|
this.power = trackPoint.getPower();
|
||||||
|
}
|
||||||
|
|
||||||
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
||||||
location = new Location(LocationManager.GPS_PROVIDER);
|
location = new Location(LocationManager.GPS_PROVIDER);
|
||||||
@@ -90,14 +88,6 @@ public class TrackPoint {
|
|||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataSet getSensorDataSet() {
|
|
||||||
return sensorDataSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSensorDataSet(SensorDataSet sensorDataSet) {
|
|
||||||
this.sensorDataSet = sensorDataSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLatitude() {
|
public double getLatitude() {
|
||||||
return location.getLatitude();
|
return location.getLatitude();
|
||||||
}
|
}
|
||||||
@@ -181,4 +171,51 @@ public class TrackPoint {
|
|||||||
public float bearingTo(@NonNull Location dest) {
|
public float bearingTo(@NonNull Location dest) {
|
||||||
return location.bearingTo(dest);
|
return location.bearingTo(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sensor data
|
||||||
|
public boolean hasSensorData() {
|
||||||
|
return hasHeartRate() || hasCyclingCadence() || hasPower();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasHeartRate() {
|
||||||
|
return heartRate_bpm != null && heartRate_bpm > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHeartRate_bpm() {
|
||||||
|
return heartRate_bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeartRate_bpm(Float heartRate_bpm) {
|
||||||
|
this.heartRate_bpm = heartRate_bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCyclingCadence() {
|
||||||
|
return cyclingCadence_rpm != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float getCyclingCadence_rpm() {
|
||||||
|
return cyclingCadence_rpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCyclingCadence_rpm(Float cyclingCadence_rpm) {
|
||||||
|
this.cyclingCadence_rpm = cyclingCadence_rpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPower() {
|
||||||
|
return power != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float getPower() {
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPower(Float power) {
|
||||||
|
this.power = power;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
String DEFAULT_SORT_ORDER = "_id";
|
String DEFAULT_SORT_ORDER = "_id";
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
|
String UUID = "uuid";
|
||||||
String NAME = "name"; // track name
|
String NAME = "name"; // track name
|
||||||
String DESCRIPTION = "description"; // track description
|
String DESCRIPTION = "description"; // track description
|
||||||
String CATEGORY = "category"; // track activity type
|
String CATEGORY = "category"; // track activity type
|
||||||
@@ -71,5 +72,9 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
+ MINELEVATION + " FLOAT, " // min elevation
|
+ MINELEVATION + " FLOAT, " // min elevation
|
||||||
+ MAXELEVATION + " FLOAT, " // max elevation
|
+ MAXELEVATION + " FLOAT, " // max elevation
|
||||||
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
+ 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 + ")";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,14 +38,16 @@ public final class Waypoint {
|
|||||||
private long trackId = -1L;
|
private long trackId = -1L;
|
||||||
private double length = 0.0;
|
private double length = 0.0;
|
||||||
private long duration = 0;
|
private long duration = 0;
|
||||||
private Location location = null;
|
private Location location;
|
||||||
|
@Deprecated //TODO Make an URI instead of String
|
||||||
private String photoUrl = "";
|
private String photoUrl = "";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Waypoint() {
|
public Waypoint(@NonNull TrackPoint trackPoint) {
|
||||||
|
this.location = trackPoint.getLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Waypoint(Location location) {
|
public Waypoint(@NonNull Location location) {
|
||||||
this.location = location;
|
this.location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public interface WaypointsColumns extends BaseColumns {
|
|||||||
|
|
||||||
String TABLE_NAME = "waypoints";
|
String TABLE_NAME = "waypoints";
|
||||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
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_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
|
||||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
|
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
|
||||||
String DEFAULT_SORT_ORDER = "_id";
|
String DEFAULT_SORT_ORDER = "_id";
|
||||||
|
|||||||
+132
-151
@@ -40,9 +40,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
|||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ContentProviderUtils} implementation.
|
* {@link ContentProviderUtils} implementation.
|
||||||
@@ -55,19 +55,13 @@ public class ContentProviderUtils {
|
|||||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||||
private static final int MAX_LATITUDE = 90000000;
|
private static final int MAX_LATITUDE = 90000000;
|
||||||
|
|
||||||
/**
|
// The authority (the first part of the URI) for the app's content provider.
|
||||||
* The authority (the first part of the URI) for the app's content provider.
|
|
||||||
*/
|
|
||||||
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||||
|
|
||||||
/**
|
// The base URI for the app's content provider.
|
||||||
* The base URI for the app's content provider.
|
|
||||||
*/
|
|
||||||
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||||
|
|
||||||
/**
|
// Maximum number of waypoints that will be loaded at one time.
|
||||||
* Maximum number of waypoints that will be loaded at one time.
|
|
||||||
*/
|
|
||||||
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||||
private static final String ID_SEPARATOR = ",";
|
private static final String ID_SEPARATOR = ",";
|
||||||
|
|
||||||
@@ -87,7 +81,7 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears a track: removes waypoints and trackpoints.
|
* Clears a track: removes waypoints and trackPoints.
|
||||||
* Only keeps the track id.
|
* Only keeps the track id.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
@@ -106,6 +100,7 @@ public class ContentProviderUtils {
|
|||||||
*/
|
*/
|
||||||
public Track createTrack(Cursor cursor) {
|
public Track createTrack(Cursor cursor) {
|
||||||
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||||
|
int uuidIndex = cursor.getColumnIndexOrThrow(TracksColumns.UUID);
|
||||||
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||||
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
|
||||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||||
@@ -125,6 +120,9 @@ public class ContentProviderUtils {
|
|||||||
if (!cursor.isNull(idIndex)) {
|
if (!cursor.isNull(idIndex)) {
|
||||||
track.setId(cursor.getLong(idIndex));
|
track.setId(cursor.getLong(idIndex));
|
||||||
}
|
}
|
||||||
|
if (!cursor.isNull(uuidIndex)) {
|
||||||
|
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
|
||||||
|
}
|
||||||
if (!cursor.isNull(nameIndex)) {
|
if (!cursor.isNull(nameIndex)) {
|
||||||
track.setName(cursor.getString(nameIndex));
|
track.setName(cursor.getString(nameIndex));
|
||||||
}
|
}
|
||||||
@@ -167,9 +165,7 @@ public class ContentProviderUtils {
|
|||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@VisibleForTesting
|
||||||
* Deletes all tracks (including waypoints and track points).
|
|
||||||
*/
|
|
||||||
public void deleteAllTracks(Context context) {
|
public void deleteAllTracks(Context context) {
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
||||||
@@ -177,7 +173,7 @@ public class ContentProviderUtils {
|
|||||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
|
|
||||||
File dir = FileUtils.getPhotoDir(context);
|
File dir = FileUtils.getPhotoDir(context);
|
||||||
deleteDirectoryRecurse(dir);
|
FileUtils.deleteDirectoryRecurse(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,13 +184,15 @@ public class ContentProviderUtils {
|
|||||||
public void deleteTrack(Context context, long trackId) {
|
public void deleteTrack(Context context, long trackId) {
|
||||||
deleteTrackPointsAndWaypoints(context, trackId);
|
deleteTrackPointsAndWaypoints(context, trackId);
|
||||||
|
|
||||||
// Delete track last since it triggers a database vaccum call
|
// Delete track folder resources.
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?",
|
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||||
new String[]{Long.toString(trackId)});
|
|
||||||
|
// Delete track last since it triggers a database vacuum call
|
||||||
|
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes track points and waypoints of a track.
|
* Deletes trackPoints and waypoints of a track.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
@@ -203,29 +201,13 @@ public class ContentProviderUtils {
|
|||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, where, selectionArgs);
|
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, where, selectionArgs);
|
||||||
|
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?",
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)});
|
||||||
new String[]{Long.toString(trackId)});
|
|
||||||
deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the directory recursively.
|
|
||||||
*
|
|
||||||
* @param dir the directory
|
|
||||||
*/
|
|
||||||
private void deleteDirectoryRecurse(File dir) {
|
|
||||||
if (dir != null && dir.exists() && dir.isDirectory()) {
|
|
||||||
for (File child : dir.listFiles()) {
|
|
||||||
deleteDirectoryRecurse(child);
|
|
||||||
}
|
|
||||||
dir.delete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all the tracks.
|
* Gets all the tracks.
|
||||||
* If no track exists, an empty list is returned.
|
*
|
||||||
* NOTE: the returned tracks do not have any track points attached.
|
* @return the tracks do not have any trackPoints attached.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public List<Track> getAllTracks() {
|
public List<Track> getAllTracks() {
|
||||||
@@ -241,9 +223,6 @@ public class ContentProviderUtils {
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the last track or null.
|
|
||||||
*/
|
|
||||||
public Track getLastTrack() {
|
public Track getLastTrack() {
|
||||||
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
|
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
|
||||||
// Using the same order as shown in the track list
|
// Using the same order as shown in the track list
|
||||||
@@ -255,10 +234,8 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a track by a track id or null
|
|
||||||
* Note that the returned track doesn't have any track points attached.
|
|
||||||
*
|
|
||||||
* @param trackId the track id.
|
* @param trackId the track id.
|
||||||
|
* @return the track doesn't have any trackPoints attached
|
||||||
*/
|
*/
|
||||||
public Track getTrack(long trackId) {
|
public Track getTrack(long trackId) {
|
||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
@@ -286,7 +263,7 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a track.
|
* Inserts a track.
|
||||||
* NOTE: This doesn't insert any track points.
|
* NOTE: This doesn't insert any trackPoints.
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
* @return the content provider URI of the inserted track.
|
* @return the content provider URI of the inserted track.
|
||||||
@@ -297,13 +274,12 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a track.
|
* Updates a track.
|
||||||
* NOTE: This doesn't update any track points.
|
* NOTE: This doesn't update any trackPoints.
|
||||||
*
|
*
|
||||||
* @param track the track
|
* @param track the track
|
||||||
*/
|
*/
|
||||||
public void updateTrack(Track track) {
|
public void updateTrack(Track track) {
|
||||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track),
|
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())});
|
||||||
TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentValues createContentValues(Track track) {
|
private ContentValues createContentValues(Track track) {
|
||||||
@@ -314,6 +290,7 @@ public class ContentProviderUtils {
|
|||||||
if (track.getId() >= 0) {
|
if (track.getId() >= 0) {
|
||||||
values.put(TracksColumns._ID, track.getId());
|
values.put(TracksColumns._ID, track.getId());
|
||||||
}
|
}
|
||||||
|
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
|
||||||
values.put(TracksColumns.NAME, track.getName());
|
values.put(TracksColumns.NAME, track.getName());
|
||||||
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
||||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||||
@@ -407,35 +384,17 @@ public class ContentProviderUtils {
|
|||||||
return waypoint;
|
return waypoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void deleteWaypoint(Context context, long waypointId) {
|
||||||
* Deletes a waypoint.
|
|
||||||
* If deleting a statistics waypoint, this will also correct the next statistics waypoint after the deleted one to reflect the deletion.
|
|
||||||
* The generator is used to update the next statistics waypoint.
|
|
||||||
*
|
|
||||||
* @param waypointId the waypoint id
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void deleteWaypoint(long waypointId) {
|
|
||||||
final Waypoint waypoint = getWaypoint(waypointId);
|
final Waypoint waypoint = getWaypoint(waypointId);
|
||||||
if (waypoint != null && waypoint.hasPhoto()) {
|
deleteWaypointPhoto(context, waypoint);
|
||||||
Uri uri = waypoint.getPhotoURI();
|
|
||||||
File file = new File(uri.getPath());
|
|
||||||
if (file.exists()) {
|
|
||||||
File parent = file.getParentFile();
|
|
||||||
file.delete();
|
|
||||||
if (parent.listFiles().length == 0) {
|
|
||||||
parent.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
|
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the next waypoint number for a type.
|
* Gets the next waypoint number.
|
||||||
* Returns -1 if not able to get the next waypoint number.
|
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
|
* @return -1 if not able to get the next waypoint number.
|
||||||
*/
|
*/
|
||||||
public int getNextWaypointNumber(long trackId) {
|
public int getNextWaypointNumber(long trackId) {
|
||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
@@ -452,12 +411,6 @@ public class ContentProviderUtils {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a waypoint from a waypoint id.
|
|
||||||
* Returns null if not found.
|
|
||||||
*
|
|
||||||
* @param waypointId the waypoint id
|
|
||||||
*/
|
|
||||||
public Waypoint getWaypoint(long waypointId) {
|
public Waypoint getWaypoint(long waypointId) {
|
||||||
if (waypointId < 0) {
|
if (waypointId < 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -478,8 +431,7 @@ public class ContentProviderUtils {
|
|||||||
* @param selection the selection. Can be null
|
* @param selection the selection. Can be null
|
||||||
* @param selectionArgs the selection arguments. Can be null
|
* @param selectionArgs the selection arguments. Can be null
|
||||||
* @param sortOrder the sort order. Can be null
|
* @param sortOrder the sort order. Can be null
|
||||||
* @param maxWaypoints the maximum number of waypoints to return. -1 for no
|
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
|
||||||
* limit
|
|
||||||
*/
|
*/
|
||||||
public Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) {
|
public Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) {
|
||||||
return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints);
|
return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints);
|
||||||
@@ -536,12 +488,13 @@ public class ContentProviderUtils {
|
|||||||
String[] projection = new String[]{"count(*) AS count"};
|
String[] projection = new String[]{"count(*) AS count"};
|
||||||
String selection = WaypointsColumns.TRACKID + "=?";
|
String selection = WaypointsColumns.TRACKID + "=?";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID);
|
try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) {
|
||||||
|
if (cursor == null) {
|
||||||
cursor.moveToFirst();
|
return 0;
|
||||||
int count = cursor.getInt(0);
|
}
|
||||||
cursor.close();
|
cursor.moveToFirst();
|
||||||
return count;
|
return cursor.getInt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -555,14 +508,38 @@ public class ContentProviderUtils {
|
|||||||
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
|
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete waypoint's photo if any.
|
||||||
|
*
|
||||||
|
* @param context the context object.
|
||||||
|
* @param waypoint the waypoint object.
|
||||||
|
*/
|
||||||
|
private void deleteWaypointPhoto(Context context, Waypoint waypoint) {
|
||||||
|
if (waypoint != null && waypoint.hasPhoto()) {
|
||||||
|
Uri uri = waypoint.getPhotoURI();
|
||||||
|
File file = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), uri);
|
||||||
|
if (file.exists()) {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
file.delete();
|
||||||
|
if (parent.listFiles().length == 0) {
|
||||||
|
parent.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a waypoint.
|
* Updates a waypoint.
|
||||||
* Returns true if successful.
|
* Returns true if successful.
|
||||||
*
|
*
|
||||||
* @param waypoint the waypoint
|
* @param updatedWaypoint the waypoint with updated data.
|
||||||
*/
|
*/
|
||||||
public boolean updateWaypoint(Waypoint waypoint) {
|
public boolean updateWaypoint(Context context, Waypoint updatedWaypoint) {
|
||||||
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())});
|
Waypoint savedWaypoint = getWaypoint(updatedWaypoint.getId());
|
||||||
|
if (!updatedWaypoint.hasPhoto()) {
|
||||||
|
deleteWaypointPhoto(context, savedWaypoint);
|
||||||
|
}
|
||||||
|
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())});
|
||||||
return rows == 1;
|
return rows == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,19 +559,17 @@ public class ContentProviderUtils {
|
|||||||
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
|
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
|
||||||
|
|
||||||
Location location = waypoint.getLocation();
|
Location location = waypoint.getLocation();
|
||||||
if (location != null) {
|
values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
|
||||||
values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
|
values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
|
||||||
values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
|
values.put(WaypointsColumns.TIME, location.getTime());
|
||||||
values.put(WaypointsColumns.TIME, location.getTime());
|
if (location.hasAltitude()) {
|
||||||
if (location.hasAltitude()) {
|
values.put(WaypointsColumns.ALTITUDE, location.getAltitude());
|
||||||
values.put(WaypointsColumns.ALTITUDE, location.getAltitude());
|
}
|
||||||
}
|
if (location.hasAccuracy()) {
|
||||||
if (location.hasAccuracy()) {
|
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
|
||||||
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
|
}
|
||||||
}
|
if (location.hasBearing()) {
|
||||||
if (location.hasBearing()) {
|
values.put(WaypointsColumns.BEARING, location.getBearing());
|
||||||
values.put(WaypointsColumns.BEARING, location.getBearing());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
|
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
|
||||||
@@ -623,8 +598,8 @@ public class ContentProviderUtils {
|
|||||||
/**
|
/**
|
||||||
* Fills a {@link TrackPoint} from a cursor.
|
* Fills a {@link TrackPoint} from a cursor.
|
||||||
*
|
*
|
||||||
* @param cursor the cursor pointing to a trackPoint.
|
* @param cursor the cursor pointing to a trackPoint.
|
||||||
* @param indexes the cached track points indexes
|
* @param indexes the cached trackPoints indexes
|
||||||
*/
|
*/
|
||||||
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
||||||
TrackPoint trackPoint = new TrackPoint();
|
TrackPoint trackPoint = new TrackPoint();
|
||||||
@@ -651,30 +626,30 @@ public class ContentProviderUtils {
|
|||||||
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
|
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
|
if (!cursor.isNull(indexes.sensorHeartRateIndex)) {
|
||||||
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
|
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
|
||||||
float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex);
|
}
|
||||||
|
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
|
||||||
trackPoint.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime()));
|
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||||
|
}
|
||||||
|
if (!cursor.isNull(indexes.sensorPowerIndex)) {
|
||||||
|
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||||
|
}
|
||||||
|
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts multiple trackPoints points.
|
* Inserts multiple trackPoints.
|
||||||
*
|
*
|
||||||
* @param trackPoints an array of trackPoints
|
* @param trackPoints an array of trackPoints
|
||||||
* @param length the number of trackPoints (from the beginning of the array) to
|
* @param trackId the trackPoints id
|
||||||
* insert, or -1 for all of them
|
* @return the number of trackPoints inserted
|
||||||
* @param trackId the trackPoints id
|
|
||||||
* @return the number of points inserted
|
|
||||||
*/
|
*/
|
||||||
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, int length, long trackId) {
|
//TODO Only used for testing and file import; might be better to replace it.
|
||||||
if (length == -1) {
|
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, long trackId) {
|
||||||
length = trackPoints.length;
|
ContentValues[] values = new ContentValues[trackPoints.length];
|
||||||
}
|
for (int i = 0; i < values.length; i++) {
|
||||||
ContentValues[] values = new ContentValues[length];
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
values[i] = createContentValues(trackPoints[i], trackId);
|
values[i] = createContentValues(trackPoints[i], trackId);
|
||||||
}
|
}
|
||||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||||
@@ -691,9 +666,7 @@ public class ContentProviderUtils {
|
|||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID
|
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||||
+ ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID
|
|
||||||
+ "=?)";
|
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
@@ -714,9 +687,7 @@ public class ContentProviderUtils {
|
|||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
|
||||||
+ ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID
|
|
||||||
+ "=?)";
|
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
@@ -727,19 +698,17 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the track point id of a location.
|
* Gets the trackPoint id for a location.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param location the location
|
* @param location the location
|
||||||
* @return track point id if the location is in the track. -1L otherwise.
|
* @return trackPoint id if the location is in the track. -1L otherwise.
|
||||||
*/
|
*/
|
||||||
public long getTrackPointId(long trackId, Location location) {
|
public long getTrackPointId(long trackId, Location location) {
|
||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
return -1L;
|
return -1L;
|
||||||
}
|
}
|
||||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
||||||
+ ") from " + TrackPointsColumns.TABLE_NAME
|
|
||||||
+ " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId), Long.toString(location.getTime())};
|
String[] selectionArgs = new String[]{Long.toString(trackId), Long.toString(location.getTime())};
|
||||||
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
@@ -762,7 +731,7 @@ public class ContentProviderUtils {
|
|||||||
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
* Creates a location cursor. The caller owns the returned cursor and is responsible for closing it.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
* @param startTrackPointId the starting trackPoint id. -1L to ignore
|
||||||
* @param maxLocations maximum number of locations to return. -1 for no limit
|
* @param maxLocations maximum number of locations to return. -1 for no limit
|
||||||
* @param descending true to sort the result in descending order (latest location first)
|
* @param descending true to sort the result in descending order (latest location first)
|
||||||
*/
|
*/
|
||||||
@@ -775,8 +744,7 @@ public class ContentProviderUtils {
|
|||||||
String[] selectionArgs;
|
String[] selectionArgs;
|
||||||
if (startTrackPointId >= 0) {
|
if (startTrackPointId >= 0) {
|
||||||
String comparison = descending ? "<=" : ">=";
|
String comparison = descending ? "<=" : ">=";
|
||||||
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison
|
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
|
||||||
+ "?";
|
|
||||||
selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)};
|
selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)};
|
||||||
} else {
|
} else {
|
||||||
selection = TrackPointsColumns.TRACKID + "=?";
|
selection = TrackPointsColumns.TRACKID + "=?";
|
||||||
@@ -804,19 +772,17 @@ public class ContentProviderUtils {
|
|||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
||||||
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
|
|
||||||
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||||
return findTrackPointBy(selection, selectionArgs);
|
return findTrackPointBy(selection, selectionArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a track point.
|
* Inserts a trackPoint.
|
||||||
*
|
*
|
||||||
* @param trackPoint the trackPoint
|
* @param trackPoint the trackPoint
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @return the content provider URI of the inserted track point
|
* @return the content provider URI of the inserted trackPoint
|
||||||
*/
|
*/
|
||||||
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
|
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
|
||||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
|
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
|
||||||
@@ -826,7 +792,7 @@ public class ContentProviderUtils {
|
|||||||
* Creates the {@link ContentValues} for a {@link TrackPoint}.
|
* Creates the {@link ContentValues} for a {@link TrackPoint}.
|
||||||
*
|
*
|
||||||
* @param trackPoint the trackPoint
|
* @param trackPoint the trackPoint
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
*/
|
*/
|
||||||
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
|
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
@@ -848,16 +814,14 @@ public class ContentProviderUtils {
|
|||||||
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
|
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
|
||||||
}
|
}
|
||||||
|
|
||||||
//SensorData
|
if (trackPoint.hasHeartRate()) {
|
||||||
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
|
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
|
||||||
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
|
|
||||||
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getSensorDataSet().getHeartRate());
|
|
||||||
}
|
}
|
||||||
if (sensorDataSet != null && sensorDataSet.hasCadence()) {
|
if (trackPoint.hasCyclingCadence()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getSensorDataSet().getCadence());
|
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
||||||
}
|
}
|
||||||
if (sensorDataSet != null && sensorDataSet.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getSensorDataSet().getPower());
|
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@@ -870,7 +834,7 @@ public class ContentProviderUtils {
|
|||||||
* When done with iteration, {@link TrackPointIterator#close()} must be called.
|
* When done with iteration, {@link TrackPointIterator#close()} must be called.
|
||||||
*
|
*
|
||||||
* @param trackId the track id
|
* @param trackId the track id
|
||||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
* @param startTrackPointId the starting trackPoint id. -1L to ignore
|
||||||
* @param descending true to sort the result in descending order (latest location first)
|
* @param descending true to sort the result in descending order (latest location first)
|
||||||
*/
|
*/
|
||||||
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) {
|
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) {
|
||||||
@@ -887,7 +851,7 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a track point cursor.
|
* Gets a trackPoint cursor.
|
||||||
*
|
*
|
||||||
* @param projection the projection
|
* @param projection the projection
|
||||||
* @param selection the selection
|
* @param selection the selection
|
||||||
@@ -898,6 +862,24 @@ public class ContentProviderUtils {
|
|||||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public List<TrackPoint> getTrackPoints(long trackId) {
|
||||||
|
List<TrackPoint> trackPoints = null;
|
||||||
|
|
||||||
|
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
|
||||||
|
if (trackPointCursor != null) {
|
||||||
|
trackPointCursor.moveToFirst();
|
||||||
|
trackPoints = new ArrayList<>(trackPointCursor.getCount());
|
||||||
|
for (int i = 0; i < trackPointCursor.getCount(); i++) {
|
||||||
|
trackPoints.add(createTrackPoint(trackPointCursor));
|
||||||
|
trackPointCursor.moveToNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackPoints;
|
||||||
|
}
|
||||||
|
|
||||||
int getDefaultCursorBatchSize() {
|
int getDefaultCursorBatchSize() {
|
||||||
return defaultCursorBatchSize;
|
return defaultCursorBatchSize;
|
||||||
}
|
}
|
||||||
@@ -915,7 +897,7 @@ public class ContentProviderUtils {
|
|||||||
/**
|
/**
|
||||||
* Formats an array of IDs as comma separated string value
|
* Formats an array of IDs as comma separated string value
|
||||||
*
|
*
|
||||||
* @param ids array with IDs
|
* @param ids array with IDs
|
||||||
* @return comma separated list of ids
|
* @return comma separated list of ids
|
||||||
*/
|
*/
|
||||||
public static String formatIdListForUri(long[] ids) {
|
public static String formatIdListForUri(long[] ids) {
|
||||||
@@ -932,5 +914,4 @@ public class ContentProviderUtils {
|
|||||||
public static String[] parseTrackIdsFromUri(Uri url) {
|
public static String[] parseTrackIdsFromUri(Uri url) {
|
||||||
return TextUtils.split(url.getLastPathSegment(), ID_SEPARATOR);
|
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.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.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
|
@Override
|
||||||
@@ -140,6 +141,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
case WAYPOINTS:
|
case WAYPOINTS:
|
||||||
return WaypointsColumns.CONTENT_TYPE;
|
return WaypointsColumns.CONTENT_TYPE;
|
||||||
case WAYPOINTS_BY_ID:
|
case WAYPOINTS_BY_ID:
|
||||||
|
case WAYPOINTS_BY_TRACKID:
|
||||||
return WaypointsColumns.CONTENT_ITEMTYPE;
|
return WaypointsColumns.CONTENT_ITEMTYPE;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown URL " + url);
|
throw new IllegalArgumentException("Unknown URL " + url);
|
||||||
@@ -219,6 +221,10 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
break;
|
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:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown url " + url);
|
throw new IllegalArgumentException("Unknown url " + url);
|
||||||
}
|
}
|
||||||
@@ -382,6 +388,7 @@ public abstract class CustomContentProvider extends ContentProvider {
|
|||||||
TRACKS,
|
TRACKS,
|
||||||
TRACKS_BY_ID,
|
TRACKS_BY_ID,
|
||||||
WAYPOINTS,
|
WAYPOINTS,
|
||||||
WAYPOINTS_BY_ID
|
WAYPOINTS_BY_ID,
|
||||||
|
WAYPOINTS_BY_TRACKID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-5
@@ -1,15 +1,20 @@
|
|||||||
package de.dennisguse.opentracks.content.provider;
|
package de.dennisguse.opentracks.content.provider;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||||
|
import de.dennisguse.opentracks.util.UUIDUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database helper for creating and upgrading the database.
|
* Database helper for creating and upgrading the database.
|
||||||
@@ -17,13 +22,11 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
private static final int DATABASE_VERSION = 25;
|
|
||||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
@VisibleForTesting
|
private static final int DATABASE_VERSION = 26;
|
||||||
private static final String DATABASE_NAME = "database.db";
|
|
||||||
|
|
||||||
|
private static final String DATABASE_NAME = "database.db";
|
||||||
|
|
||||||
public CustomSQLiteOpenHelper(Context context) {
|
public CustomSQLiteOpenHelper(Context context) {
|
||||||
this(context, DATABASE_NAME);
|
this(context, DATABASE_NAME);
|
||||||
@@ -45,6 +48,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
||||||
|
|
||||||
db.execSQL(TracksColumns.CREATE_TABLE);
|
db.execSQL(TracksColumns.CREATE_TABLE);
|
||||||
|
db.execSQL(TracksColumns.CREATE_TABLE_INDEX);
|
||||||
|
|
||||||
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
||||||
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
||||||
@@ -61,6 +65,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 25:
|
case 25:
|
||||||
upgradeFrom24to25(db);
|
upgradeFrom24to25(db);
|
||||||
break;
|
break;
|
||||||
|
case 26:
|
||||||
|
upgradeFrom25to26(db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
@@ -79,6 +86,9 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 24:
|
case 24:
|
||||||
downgradeFrom25to24(db);
|
downgradeFrom25to24(db);
|
||||||
break;
|
break;
|
||||||
|
case 25:
|
||||||
|
downgradeFrom26to25(db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
@@ -107,7 +117,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void downgradeFrom24to23(SQLiteDatabase db) {
|
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.beginTransaction();
|
||||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
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("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.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
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.Cursor;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -92,7 +91,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
|
|
||||||
static long[] parseURI(Uri uri) {
|
static long[] parseURI(Uri uri) {
|
||||||
List<String> uriPaths = uri.getPathSegments();
|
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.");
|
Log.d(TAG, "URI does not contain any trackIds.");
|
||||||
return new long[]{};
|
return new long[]{};
|
||||||
}
|
}
|
||||||
@@ -209,16 +208,13 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
|||||||
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
|
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>() {
|
PipeDataWriter<String> pipeDataWriter = (PipeDataWriter<String>) (output, uri1, mimeType, opts, args) -> {
|
||||||
@Override
|
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||||
public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable String args) {
|
trackExporter.writeTrack(getContext(), fileOutputStream);
|
||||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
} catch (IOException e) {
|
||||||
trackExporter.writeTrack(getContext(), fileOutputStream);
|
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||||
} 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() {
|
public void remove() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
if (cursor == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor.getCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
|
|
||||||
|
public class SensorData {
|
||||||
|
|
||||||
|
private final String sensorAddress;
|
||||||
|
private final String sensorName;
|
||||||
|
|
||||||
|
private final long timestamp_ms;
|
||||||
|
|
||||||
|
SensorData(String sensorAddress, String sensorName) {
|
||||||
|
this(sensorAddress, sensorName, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
SensorData(String sensorAddress, String sensorName, long timestamp_ms) {
|
||||||
|
this.sensorAddress = sensorAddress;
|
||||||
|
this.sensorName = sensorName;
|
||||||
|
this.timestamp_ms = timestamp_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSensorAddress() {
|
||||||
|
return sensorAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSensorName() {
|
||||||
|
return sensorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the data recent considering the current time.
|
||||||
|
*/
|
||||||
|
public boolean isRecent() {
|
||||||
|
return timestamp_ms + BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS > System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.UintUtils;
|
||||||
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Cycling Cadence and Speed sensors.
|
||||||
|
* <p>
|
||||||
|
* https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=261449
|
||||||
|
*/
|
||||||
|
public final class SensorDataCycling {
|
||||||
|
|
||||||
|
private static final String TAG = SensorDataCycling.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int INVALID_VALUE_INT = -1;
|
||||||
|
private static final float INVALID_VALUE_FLOAT = Float.NaN;
|
||||||
|
|
||||||
|
private SensorDataCycling() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cadence extends SensorData {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.crankRevolutionsCount = crankRevolutionsCount;
|
||||||
|
this.crankRevolutionsTime = crankRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround for Wahoo CADENCE: provides speed instead of cadence
|
||||||
|
*/
|
||||||
|
public Cadence(@NonNull SensorDataCycling.Speed speed) {
|
||||||
|
this(speed.getSensorAddress(), speed.getSensorName(), speed.getWheelRevolutionsCount(), speed.getWheelRevolutionsTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return crankRevolutionsCount != INVALID_VALUE_INT && crankRevolutionsTime != INVALID_VALUE_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCrankRevolutionsCount() {
|
||||||
|
return crankRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCrankRevolutionsTime() {
|
||||||
|
return crankRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCadence_rpm() {
|
||||||
|
return !Float.isNaN(cadence_rpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCadence_rpm() {
|
||||||
|
return cadence_rpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compute(Cadence previous) {
|
||||||
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
|
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 / timeDiff_ms;
|
||||||
|
cadence_rpm = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "cadence=" + getCadence_rpm() + " time=" + getCrankRevolutionsTime() + " count=" + getCrankRevolutionsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof Cadence)) return false;
|
||||||
|
|
||||||
|
Cadence comp = (Cadence) obj;
|
||||||
|
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Speed extends SensorData {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.wheelRevolutionsCount = wheelRevolutionsCount;
|
||||||
|
this.wheelRevolutionsTime = wheelRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasData() {
|
||||||
|
return wheelRevolutionsCount != INVALID_VALUE_INT && wheelRevolutionsTime != INVALID_VALUE_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWheelRevolutionsCount() {
|
||||||
|
return wheelRevolutionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWheelRevolutionsTime() {
|
||||||
|
return wheelRevolutionsTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSpeed_mps() {
|
||||||
|
return !Float.isNaN(speed_mps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getSpeed_mps() {
|
||||||
|
return speed_mps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compute(Speed previous, int wheel_circumference_mm) {
|
||||||
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
||||||
|
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
|
||||||
|
speed_mps = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M / timeDiff_s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "speed=" + getSpeed_mps() + " time=" + getWheelRevolutionsTime() + " count=" + getWheelRevolutionsCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof Speed)) return false;
|
||||||
|
|
||||||
|
Speed comp = (Speed) obj;
|
||||||
|
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CadenceAndSpeed extends SensorData {
|
||||||
|
|
||||||
|
private final Cadence cadence;
|
||||||
|
private final Speed speed;
|
||||||
|
|
||||||
|
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
|
||||||
|
super(sensorAddress, sensorName);
|
||||||
|
this.cadence = cadence;
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cadence getCadence() {
|
||||||
|
return cadence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Speed getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class SensorDataHeartRate extends SensorData {
|
||||||
|
|
||||||
|
private final float heartRate_bpm;
|
||||||
|
|
||||||
|
public SensorDataHeartRate(String name, String address, float heartRate_bpm) {
|
||||||
|
super(name, address);
|
||||||
|
this.heartRate_bpm = heartRate_bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasHeartRate_bpm() {
|
||||||
|
return !Float.isNaN(heartRate_bpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHeartRate_bpm() {
|
||||||
|
return heartRate_bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "heart=" + heartRate_bpm;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,94 +1,75 @@
|
|||||||
package de.dennisguse.opentracks.content.sensor;
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
public final class SensorDataSet {
|
public final class SensorDataSet {
|
||||||
|
|
||||||
public static final float DATA_UNAVAILABLE = Float.NaN;
|
private SensorDataHeartRate heartRate;
|
||||||
|
|
||||||
//TODO It might be necessary to consider: if sensor was connected as well.
|
private SensorDataCycling.Cadence cyclingCadence;
|
||||||
private String sensorName;
|
|
||||||
private String sensorAddress;
|
|
||||||
private float heartRate;
|
|
||||||
private float cadence;
|
|
||||||
private float power;
|
|
||||||
private float batteryLevel;
|
|
||||||
private long creationTimestamp;
|
|
||||||
|
|
||||||
public SensorDataSet(float heartRate, float cadence, float power, float batteryLevel, long creationTimestamp) {
|
private SensorDataCycling.Speed cyclingSpeed;
|
||||||
this.heartRate = heartRate;
|
|
||||||
this.cadence = cadence;
|
public SensorDataSet() {
|
||||||
this.power = power;
|
|
||||||
this.batteryLevel = batteryLevel;
|
|
||||||
this.creationTimestamp = creationTimestamp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataSet(float heartRate, float cadence, float power, float batteryLevel) {
|
public SensorDataHeartRate getHeartRate() {
|
||||||
this(heartRate, cadence, power, batteryLevel, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SensorDataSet(float heartRate, float cadence, float power) {
|
|
||||||
this(heartRate, cadence, power, DATA_UNAVAILABLE, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SensorDataSet(float heartRate, float cadence) {
|
|
||||||
this(heartRate, cadence, DATA_UNAVAILABLE, DATA_UNAVAILABLE, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public SensorDataSet(float heartRate, String sensorName, String sensorAddress) {
|
|
||||||
this(heartRate, DATA_UNAVAILABLE, DATA_UNAVAILABLE, DATA_UNAVAILABLE, System.currentTimeMillis());
|
|
||||||
this.sensorName = sensorName;
|
|
||||||
this.sensorAddress = sensorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasHeartRate() {
|
|
||||||
return !Float.isNaN(heartRate) && heartRate > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getHeartRate() {
|
|
||||||
return heartRate;
|
return heartRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCadence() {
|
public SensorDataCycling.Cadence getCyclingCadence() {
|
||||||
return !Float.isNaN(cadence);
|
return cyclingCadence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getCadence() {
|
public SensorDataCycling.Speed getCyclingSpeed() {
|
||||||
return cadence;
|
return cyclingSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPower() {
|
public void set(SensorData data) {
|
||||||
return !Float.isNaN(power);
|
if (data instanceof SensorDataHeartRate) {
|
||||||
|
this.heartRate = (SensorDataHeartRate) data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data instanceof SensorDataCycling.Cadence) {
|
||||||
|
this.cyclingCadence = (SensorDataCycling.Cadence) data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data instanceof SensorDataCycling.Speed) {
|
||||||
|
this.cyclingSpeed = (SensorDataCycling.Speed) data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getPower() {
|
public void clear() {
|
||||||
return power;
|
this.heartRate = null;
|
||||||
|
this.cyclingCadence = null;
|
||||||
|
this.cyclingSpeed = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getCreationTime() {
|
public void fillTrackPoint(TrackPoint trackPoint) {
|
||||||
return creationTimestamp;
|
if (heartRate != null) {
|
||||||
|
trackPoint.setHeartRate_bpm(heartRate.getHeartRate_bpm());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cyclingCadence != null && cyclingCadence.hasCadence_rpm()) {
|
||||||
|
trackPoint.setCyclingCadence_rpm(cyclingCadence.getCadence_rpm());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cyclingSpeed != null && cyclingSpeed.hasSpeed_mps()) {
|
||||||
|
trackPoint.setSpeed(cyclingSpeed.getSpeed_mps());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NonNull
|
||||||
* Is the data recent considering the current time.
|
@Override
|
||||||
*
|
public String toString() {
|
||||||
* @param maxAge the maximal age in milliseconds.
|
return (getHeartRate() != null ? "" + getHeartRate() : "")
|
||||||
*/
|
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
|
||||||
public boolean isRecent(long maxAge) {
|
+ (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "");
|
||||||
return creationTimestamp + maxAge > System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasBatteryLevel() {
|
|
||||||
return !Float.isNaN(batteryLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getBatteryLevel() {
|
|
||||||
return batteryLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSensorName() {
|
|
||||||
return sensorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSensorAddress() {
|
|
||||||
return sensorAddress;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.content.sensor;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
|
|
||||||
public enum SensorState {
|
|
||||||
NONE,
|
|
||||||
CONNECTING,
|
|
||||||
CONNECTED,
|
|
||||||
DISCONNECTING,
|
|
||||||
DISCONNECTED;
|
|
||||||
|
|
||||||
public static String getStateAsString(SensorState state, Context c) {
|
|
||||||
switch (state) {
|
|
||||||
case NONE:
|
|
||||||
return c.getString(R.string.value_none);
|
|
||||||
case CONNECTING:
|
|
||||||
return c.getString(R.string.sensor_state_connecting);
|
|
||||||
case CONNECTED:
|
|
||||||
return c.getString(R.string.sensor_state_connected);
|
|
||||||
case DISCONNECTING:
|
|
||||||
case DISCONNECTED:
|
|
||||||
return c.getString(R.string.sensor_state_disconnected);
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,7 +32,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
||||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||||
import de.dennisguse.opentracks.chart.ChartView;
|
import de.dennisguse.opentracks.chart.ChartView;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
@@ -75,6 +75,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
//TODO Why is this needed?
|
//TODO Why is this needed?
|
||||||
private int recordingDistanceInterval;
|
private int recordingDistanceInterval;
|
||||||
|
private String category = "";
|
||||||
|
|
||||||
// Modes of operation
|
// Modes of operation
|
||||||
private boolean chartByDistance;
|
private boolean chartByDistance;
|
||||||
@@ -89,28 +90,22 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
if (metricUnits != chartView.getMetricUnits()) {
|
if (metricUnits != chartView.getMetricUnits()) {
|
||||||
chartView.setMetricUnits(metricUnits);
|
chartView.setMetricUnits(metricUnits);
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.requestLayout();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||||
if (reportSpeed != chartView.getReportSpeed()) {
|
if (reportSpeed != chartView.getReportSpeed()) {
|
||||||
chartView.setReportSpeed(reportSpeed);
|
chartView.setReportSpeed(reportSpeed);
|
||||||
chartView.applyReportSpeed();
|
chartView.applyReportSpeed();
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.requestLayout();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,8 +188,16 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
if (track == null || track.getTrackStatistics() == null) {
|
if (track == null || track.getTrackStatistics() == null) {
|
||||||
startTime = -1L;
|
startTime = -1L;
|
||||||
|
category = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
category = track.getCategory();
|
||||||
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext(), category);
|
||||||
|
if (reportSpeed != chartView.getReportSpeed()) {
|
||||||
|
chartView.setReportSpeed(reportSpeed);
|
||||||
|
chartView.applyReportSpeed();
|
||||||
|
}
|
||||||
startTime = track.getTrackStatistics().getStartTime_ms();
|
startTime = track.getTrackStatistics().getStartTime_ms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,12 +208,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
||||||
pendingPoints.clear();
|
pendingPoints.clear();
|
||||||
chartView.reset();
|
chartView.reset();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
if (isResumed()) {
|
||||||
public void run() {
|
chartView.resetScroll();
|
||||||
if (isResumed()) {
|
|
||||||
chartView.resetScroll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||||
*/
|
*/
|
||||||
private synchronized void resumeTrackDataHub() {
|
private synchronized void resumeTrackDataHub() {
|
||||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
|
||||||
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
|
ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
return new ChartPoint(trackStatisticsUpdater, trackPoint.getSensorDataSet(), chartByDistance, chartView.getMetricUnits());
|
return new ChartPoint(trackStatisticsUpdater, trackPoint, chartByDistance, chartView.getMetricUnits());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments;
|
|||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -43,7 +42,7 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
|||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* 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) {
|
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@@ -69,17 +68,14 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
|||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
|
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
|
||||||
|
|
||||||
int titleId;
|
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
|
||||||
int messageId;
|
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
|
||||||
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;
|
|
||||||
return DialogUtils.createConfirmationDialog(
|
return DialogUtils.createConfirmationDialog(
|
||||||
getActivity(), titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
getActivity(),
|
||||||
@Override
|
titleId,
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
getString(messageId),
|
||||||
caller.onConfirmDeleteDone(trackIds);
|
(dialog, which) -> caller.onConfirmDeleteDone(trackIds)
|
||||||
}
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,12 +85,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
|
|||||||
*/
|
*/
|
||||||
public interface ConfirmDeleteCaller {
|
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);
|
void onConfirmDeleteDone(long[] trackIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.fragments;
|
|||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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;
|
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
|
||||||
}
|
}
|
||||||
return DialogUtils.createConfirmationDialog(
|
return DialogUtils.createConfirmationDialog(
|
||||||
fragmentActivity, titleId, getString(messageId), new DialogInterface.OnClickListener() {
|
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
|
||||||
@Override
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
for (long markerId : markerIds) {
|
||||||
new Thread(new Runnable() {
|
contentProviderUtils.deleteWaypoint(getContext(), markerId);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
|
|
||||||
for (long markerId : markerIds) {
|
|
||||||
contentProviderUtils.deleteWaypoint(markerId);
|
|
||||||
}
|
|
||||||
caller.onDeleteMarkerDone();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
});
|
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);
|
long markerId = getArguments().getLong(KEY_MARKER_ID);
|
||||||
if (markerId == -1L) {
|
if (markerId == -1L) {
|
||||||
Log.d(TAG, "invalid marker id");
|
Log.d(TAG, "invalid marker id");
|
||||||
getFragmentManager().popBackStack();
|
getParentFragmentManager().popBackStack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contentProviderUtils = new ContentProviderUtils(getActivity());
|
contentProviderUtils = new ContentProviderUtils(getActivity());
|
||||||
@@ -125,16 +125,13 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||||
|
|
||||||
photoView.setOnClickListener(new View.OnClickListener() {
|
photoView.setOnClickListener(v -> {
|
||||||
@Override
|
handler.removeCallbacks(hideText);
|
||||||
public void onClick(View v) {
|
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
||||||
handler.removeCallbacks(hideText);
|
textGradient.setVisibility(visibility);
|
||||||
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
|
waypointInfo.setVisibility(visibility);
|
||||||
textGradient.setVisibility(visibility);
|
if (visibility == View.VISIBLE) {
|
||||||
waypointInfo.setVisibility(visibility);
|
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||||
if (visibility == View.VISIBLE) {
|
|
||||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return view;
|
return view;
|
||||||
@@ -222,7 +219,7 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
|
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
|
||||||
if (waypoint == null) {
|
if (waypoint == null) {
|
||||||
Log.d(TAG, "waypoint is null");
|
Log.d(TAG, "waypoint is null");
|
||||||
getFragmentManager().popBackStack();
|
getParentFragmentManager().popBackStack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user