Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae2a97576b | |||
| 3e830a086e | |||
| 618a1df3ea | |||
| 90cfc67c59 | |||
| cecc586868 | |||
| f956e7fab3 | |||
| 128b95cd83 | |||
| 895a5c2b2a | |||
| 91ce5cf8d7 | |||
| a283464331 | |||
| 1c3a2bd2e4 | |||
| 0b326a77c2 | |||
| d68008941e | |||
| b822d05a78 | |||
| 74bfed768c | |||
| ebfcce6d8d | |||
| d97d8f85e4 | |||
| 37c2c5029e | |||
| 105ff89fd8 | |||
| 0ac033106c | |||
| 21ec9fc568 | |||
| 2e3a7a31d4 | |||
| ecd7405a8c | |||
| 28abe360cb | |||
| 9cf88c00b0 | |||
| 8bbde4aacd | |||
| 00351136f2 | |||
| 30b85bd215 | |||
| 14b705b43e | |||
| 31fc0adabc | |||
| 1714da5d3b | |||
| 6ae47b5e01 | |||
| df8e4c2e2e | |||
| 6f9fa3719a | |||
| b9014c7219 | |||
| d44ab44620 | |||
| bcffe05744 | |||
| ed60734868 | |||
| e314ff8f43 | |||
| 1108684d06 | |||
| 6c0033c107 | |||
| 8a3e50ef9b | |||
| 05b572253e | |||
| f755a086cd | |||
| b0605323da | |||
| 585c2234eb | |||
| c5d7ea4245 | |||
| 281cd8a861 | |||
| 36400c8ad0 | |||
| fb00f697c5 | |||
| f529726218 | |||
| 059f3d8f43 | |||
| cae716925f | |||
| d29ee6da3d | |||
| 033b5211ea | |||
| 5f4f5baba4 | |||
| 4421046556 | |||
| 30482d5a34 | |||
| f12094c953 | |||
| c5147e7664 | |||
| 03b66bc6a2 | |||
| 7b40f2c949 | |||
| 785a922d03 | |||
| 5d2414977b | |||
| f9b8e46b32 | |||
| c79fc2f353 | |||
| e283e993bb | |||
| 3f1e74e71e | |||
| 300453e3e6 | |||
| ac8eea377e | |||
| cc44e8e9bf | |||
| 3860472e6b | |||
| 69029486b6 | |||
| b407f86d5e | |||
| 731adc7e31 | |||
| 858149e718 | |||
| 34e796f73c | |||
| 3ac71361b8 | |||
| e409b315b4 | |||
| 24eb768707 | |||
| 17ad13d559 | |||
| 3b71212905 | |||
| 16a29c5e31 | |||
| e421164282 | |||
| de01157964 | |||
| 8ef51ec318 | |||
| d2139d70ad | |||
| d1a37366be | |||
| 52d7c8a6cb | |||
| c62e56e5a3 | |||
| 6b89092adf | |||
| 118a56b8d6 | |||
| 7fb993014c | |||
| 09f72af66c | |||
| 815f111ca6 | |||
| 40d2bf0c22 | |||
| a77b7a0626 | |||
| 1a00b4b779 | |||
| 6c5c47240c | |||
| 3c32da970b | |||
| 9cff62c5ba | |||
| 424f93b4da | |||
| 3a23a412c8 | |||
| 9d64989ef1 | |||
| da62d26409 | |||
| a350077e5c | |||
| 36b6cd3c98 | |||
| 203b457ad0 | |||
| 0873b3bd12 |
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
liberapay: OpenTracks
|
liberapay: OpenTracks
|
||||||
github: [rgmf, pstorch, dennisguse]
|
github: [rgmf, pstorch, dennisguse]
|
||||||
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
custom: [ https://liberapay.com/OpenTracks, https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore ]
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
<th>Free (F-Droid)</th>
|
<th>Free (F-Droid)</th>
|
||||||
<th>Free (Nightly for F-Droid)</th>
|
<th>Free (Nightly for F-Droid)</th>
|
||||||
<th>Donations</th>
|
<th>Donations</th>
|
||||||
<th>Translations</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
@@ -27,47 +26,26 @@ _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 align="center">
|
|
||||||
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
|
|
||||||
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/svg-badge.svg" alt="Translation status" />
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<img alt="OpenTracks version published on F-Droid" src="https://img.shields.io/f-droid/v/de.dennisguse.opentracks.svg" align="middle" >
|
<img alt="OpenTracks version published on F-Droid" src="https://img.shields.io/f-droid/v/de.dennisguse.opentracks.svg" align="middle" >
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td align="center">
|
|
||||||
<a href="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc">
|
|
||||||
<img alt="bitcoin:bc1qckpls3lpjlcpyxzm66hcy65hnesas9zgacy6yc" src="promotion/donations-bitcoin.png" height="90" align="middle">
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
|
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
|
||||||
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
|
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td align="center">
|
|
||||||
<a href="https://www.amazon.de/dp/B09K2KNQ7X/">
|
|
||||||
<img alt="Get it on Amazon App Store" src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-white.png" height="40" align="middle">
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
|
||||||
|
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/horizontal-auto.svg" alt="Translation status" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png">
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png">
|
||||||
@@ -130,6 +108,7 @@ The API can be invoked by sending an explicit Intent to start an activity.
|
|||||||
|
|
||||||
`Class`:
|
`Class`:
|
||||||
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
|
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
|
||||||
|
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
|
||||||
|
|
||||||
For testing via adb: `adb shell am start -n "package/class"`
|
For testing via adb: `adb shell am start -n "package/class"`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -e
|
|||||||
# Folder where svg screenshots are expected.
|
# Folder where svg screenshots are expected.
|
||||||
SRC_DIR="screenshots-svg/"
|
SRC_DIR="screenshots-svg/"
|
||||||
# Folder where png files will be saved.
|
# Folder where png files will be saved.
|
||||||
DST_DIR="fastlane/metadata/android/en-US/phoneScreenshots/"
|
DST_DIR="fastlane/metadata/android/en-US/images/phoneScreenshots/"
|
||||||
|
|
||||||
echo "Exporting svg files to png..."
|
echo "Exporting svg files to png..."
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdkVersion 31
|
compileSdkVersion 32
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
@@ -59,13 +59,13 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 4816
|
versionCode 4938
|
||||||
versionName "v3.27.0"
|
versionName "v4.0.2"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 32
|
||||||
|
|
||||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
@@ -119,18 +119,18 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.5.0'
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
androidTestImplementation 'androidx.test:core:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:4.5.1'
|
androidTestImplementation 'org.mockito:mockito-android:4.6.1'
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
<svg xmlns:cc="http://creativecommons.org/ns#"
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-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"
|
||||||
enable-background="new 0 0 95.334 100"
|
enable-background="new 0 0 95.334 100"
|
||||||
height="100"
|
height="100"
|
||||||
id="Ebene_1"
|
id="Ebene_1"
|
||||||
@@ -9,28 +14,105 @@
|
|||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
width="100"
|
width="100"
|
||||||
x="0px"
|
x="0px"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
y="0px"
|
y="0px"
|
||||||
xml:space="preserve"><metadata
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="LOGO.svg"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
id="namedview10"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="7.94"
|
||||||
|
inkscape:cx="50"
|
||||||
|
inkscape:cy="50"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="Ebene_1" /><metadata
|
||||||
id="metadata16"><rdf:RDF><cc:Work
|
id="metadata16"><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 /></cc:Work></rdf:RDF></metadata>
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
<defs id="defs14" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata>
|
||||||
<g id="g9"
|
<defs
|
||||||
transform="translate(-1.0593197,1.9067775)">
|
id="defs14"><linearGradient
|
||||||
<linearGradient gradientUnits="userSpaceOnUse"
|
inkscape:collect="always"
|
||||||
|
id="linearGradient862"><stop
|
||||||
|
style="stop-color:#ff6d00;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop858" /><stop
|
||||||
|
style="stop-color:#ff6d00;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop860" /></linearGradient><linearGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
id="SVGID_1_"
|
id="SVGID_1_"
|
||||||
x1="28.7554"
|
x1="28.7554"
|
||||||
x2="82.789497"
|
x2="82.789497"
|
||||||
y1="81.535202"
|
y1="81.535202"
|
||||||
y2="9.2979002">
|
y2="9.2979002"
|
||||||
<stop style="stop-color:#FBB22F"
|
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#FBB22F;fill-opacity:1;fill:url(#linearGradient849)"
|
||||||
|
id="stop853"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#F58820;fill-opacity:1;fill:url(#linearGradient849)"
|
||||||
|
id="stop855"
|
||||||
|
offset="1" />
|
||||||
|
</linearGradient><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient847"><stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop843" /><stop
|
||||||
|
style="stop-color:#000000;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop845" /></linearGradient><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient847"
|
||||||
|
id="linearGradient849"
|
||||||
|
x1="83.161415"
|
||||||
|
y1="8.9067831"
|
||||||
|
x2="10.194695"
|
||||||
|
y2="92.068085"
|
||||||
|
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient862"
|
||||||
|
id="linearGradient866"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="62.43222"
|
||||||
|
y1="35.349697"
|
||||||
|
x2="9.7942638"
|
||||||
|
y2="93.02298" /></defs>
|
||||||
|
<g
|
||||||
|
id="g9"
|
||||||
|
transform="translate(-1.0593197,1.9067775)"
|
||||||
|
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||||
|
<linearGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="SVGID_1_-3"
|
||||||
|
x1="28.7554"
|
||||||
|
x2="82.789497"
|
||||||
|
y1="81.535202"
|
||||||
|
y2="9.2979002"
|
||||||
|
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#FBB22F;fill-opacity:1;fill:url(#linearGradient849)"
|
||||||
id="stop2"
|
id="stop2"
|
||||||
offset="0" />
|
offset="0" />
|
||||||
<stop style="stop-color:#F58820"
|
<stop
|
||||||
|
style="stop-color:#F58820;fill-opacity:1;fill:url(#linearGradient849)"
|
||||||
id="stop4"
|
id="stop4"
|
||||||
offset="1" />
|
offset="1" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<path style="fill:url(#SVGID_1_)"
|
<path
|
||||||
|
style="fill:url(#linearGradient866);fill-opacity:1"
|
||||||
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
||||||
id="path7" />
|
id="path7" />
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
enable-background="new 0 0 95.334 100"
|
||||||
|
height="100"
|
||||||
|
id="Ebene_1"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
width="100"
|
||||||
|
x="0px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
y="0px"
|
||||||
|
xml:space="preserve"><metadata
|
||||||
|
id="metadata16"><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 /></cc:Work></rdf:RDF></metadata>
|
||||||
|
<defs id="defs14" />
|
||||||
|
<g id="g9"
|
||||||
|
transform="translate(-1.0593197,1.9067775)">
|
||||||
|
<linearGradient gradientUnits="userSpaceOnUse"
|
||||||
|
id="SVGID_1_"
|
||||||
|
x1="28.7554"
|
||||||
|
x2="82.789497"
|
||||||
|
y1="81.535202"
|
||||||
|
y2="9.2979002">
|
||||||
|
<stop style="stop-color:#FBB22F"
|
||||||
|
id="stop2"
|
||||||
|
offset="0" />
|
||||||
|
<stop style="stop-color:#F58820"
|
||||||
|
id="stop4"
|
||||||
|
offset="1" />
|
||||||
|
</linearGradient>
|
||||||
|
<path style="fill:url(#SVGID_1_)"
|
||||||
|
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
||||||
|
id="path7" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,67 @@
|
|||||||
|
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
sodipodi:docname="ic_button_done.svg"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
id="namedview9"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.2457711"
|
||||||
|
inkscape:cx="104.26334"
|
||||||
|
inkscape:cy="71.321036"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<defs
|
||||||
|
id="defs6" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#ff6d00;stroke-width:5.03185;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path12"
|
||||||
|
r="59.984074" />
|
||||||
|
<metadata
|
||||||
|
id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<circle
|
||||||
|
style="fill:#ff6d00;fill-opacity:1;stroke:none;stroke-width:6.91728;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path835"
|
||||||
|
r="50" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:6.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 38.620483,63.865643 52.899055,78.144215 86.379516,44.663754"
|
||||||
|
id="path1000" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
height="125"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 125 125"
|
||||||
|
width="125"
|
||||||
|
sodipodi:docname="ic_button_resume.svg"
|
||||||
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
id="namedview9"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.176"
|
||||||
|
inkscape:cx="85.245821"
|
||||||
|
inkscape:cy="70.9649"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<defs
|
||||||
|
id="defs6" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#ff6d00;stroke-width:5.03185;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path12"
|
||||||
|
r="59.984074" />
|
||||||
|
<circle
|
||||||
|
style="fill:#ff6d00;fill-opacity:1;stroke:none;stroke-width:6.91728;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
cx="62.5"
|
||||||
|
cy="62.5"
|
||||||
|
id="path835"
|
||||||
|
r="50" />
|
||||||
|
<metadata
|
||||||
|
id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:title></dc:title>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:6.67087;stroke-linecap:round;stroke-opacity:1"
|
||||||
|
id="path837"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="61.453945"
|
||||||
|
sodipodi:cy="62.5"
|
||||||
|
sodipodi:r1="26.146271"
|
||||||
|
sodipodi:r2="13.073136"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="M 87.600216,62.5 67.990513,73.821668 48.38081,85.143335 l -10e-7,-22.643336 10e-7,-22.643334 19.609704,11.321667 z"
|
||||||
|
inkscape:transform-center-x="-6.5365678" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -4,3 +4,4 @@ Changes:
|
|||||||
- Configurable filename for export/sharing (default: {starttime}_{name}.{suffix})
|
- Configurable filename for export/sharing (default: {starttime}_{name}.{suffix})
|
||||||
- Vertical GPS accuracy is now stored and can be exported/imported
|
- Vertical GPS accuracy is now stored and can be exported/imported
|
||||||
(API level >= 26).
|
(API level >= 26).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
v4.0.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- UI refactoring with a Bottom App Bar.
|
||||||
|
- Public API: implemented STOP properly.
|
||||||
|
- Cleanup: use explicit preference identifiers for TrackFileFormat.
|
||||||
|
- Improvements on BLE handler.
|
||||||
|
- Add exception handler and show error activity so users can send reports about bugs.
|
||||||
|
- Android 13: launcher icon, provide a themed icon.
|
||||||
|
- Change track title capitalization to sentences.
|
||||||
|
- Translations updated.
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Sharing track added file suffix twice.
|
||||||
|
- Unit system not loaded correctly.
|
||||||
|
- File format template was not loaded (NPE).
|
||||||
|
- Public API: respect post-workout export.
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Move to androix.constraintlayout.
|
||||||
|
- UI tests slightly more robust.
|
||||||
|
- Refactor permission request.
|
||||||
|
- Upgrade to gradlew 7.4.2
|
||||||
|
- Bump material from 1.5.0 to 1.6.0
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v4.0.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Voice announcements are now configurable
|
||||||
|
- Fullscreen mode now also hides Android's navigation bar
|
||||||
|
- Connection to BLE sensors should now be faster
|
||||||
|
- Allow to reset settings while recording
|
||||||
|
- Allow to change import/export settings while recording
|
||||||
|
- KML/KMZ: do not use icons to be loaded from Google
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- TrackStoppedActivity now works on smaller screens
|
||||||
|
- Requests permissions on track list
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
v4.0.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Add nautical miles
|
||||||
|
- Settings can be reset while recording
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Voice announcement rounding
|
||||||
|
- Chart: elevation was weirdly smoothed
|
||||||
|
- Export was not checking if file exists
|
||||||
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 812 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 630 KiB After Width: | Height: | Size: 695 KiB |
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 509 KiB |
|
Before Width: | Height: | Size: 798 KiB After Width: | Height: | Size: 872 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
v3.20.2 : OpenTracks
|
||||||
|
|
||||||
|
Changements :
|
||||||
|
- Le gain et la perte d'altitude sont des entiers au lieu de floats
|
||||||
|
- Demander la permission avant d'utiliser Bluetooth par un message Toast
|
||||||
|
|
||||||
|
Correction d'un bug :
|
||||||
|
- Définir FLAG_IMMUTABLE pour PendingIntents pour éviter un crash sur Android 12
|
||||||
|
- Le capteur de cadence renvoie 0 au lieu de None lorsque l'utilisateur ne pédale pas.
|
||||||
|
- Le capteur de vitesse renvoie 0 au lieu de la dernière vitesse connue quand c'est arrêté
|
||||||
|
- Avec les capteurs, le temps de déplacement s'arrête lorsqu'il n'est pas en mouvement
|
||||||
@@ -9,3 +9,9 @@ Fonctionnalités :
|
|||||||
* Prise en charge des capteurs Bluetooth LE : rythme cardiaque, vitesse et distance (vélo), cadence de pédalage (vélo), et capteur de puissance (vélo)
|
* Prise en charge des capteurs Bluetooth LE : rythme cardiaque, vitesse et distance (vélo), cadence de pédalage (vélo), et capteur de puissance (vélo)
|
||||||
* Export : export des traces au format KMZ (photos incluses), KML ou GPX
|
* Export : export des traces au format KMZ (photos incluses), KML ou GPX
|
||||||
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
* Pas d'accès internet requis : Internet n'est pas nécessaire pour le fonctionnement de l'application
|
||||||
|
* Thème sombre ou claire, avec le respect des paramètres du système
|
||||||
|
* Pas de publicités
|
||||||
|
|
||||||
|
Logiciel Open Source
|
||||||
|
Vous pouvez utiliser, étudier, changer et partager le code source.
|
||||||
|
Licence Apache 2.0
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
v3.18.6: OpenTracks
|
||||||
|
|
||||||
|
Wijzigingen:
|
||||||
|
- "Toon op kaart"-functionaliteit via KML in plaats van KMZ
|
||||||
|
- Spraakaankondigingen bevatten geen app naam
|
||||||
|
- Tijdens opname: hoogtewinst/-verlies wordt elke seconde bijgewerkt
|
||||||
|
- Winkel beschrijvingen vertaald
|
||||||
|
|
||||||
|
Bufixes:
|
||||||
|
- Ongeldige vertalingen van stemboodschappen opgelost
|
||||||
|
|
||||||
|
Ontwikkelaar:
|
||||||
|
- Android12-toestemmingen (BLUETOOTH_SCAN)
|
||||||
|
- Android12-toestemmingen (ACCESS_COARSE_LOCATION)
|
||||||
|
- Assertiviteit voor TrackPoints (testen)
|
||||||
|
- Ongebruikte vertalingen verwijderd
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
v3.19.0: OpenTracks
|
||||||
|
|
||||||
|
Wijzigingen:
|
||||||
|
- Ondersteuning voor Running Speed en Cadence (RCS) sensoren
|
||||||
|
- Bij het delen van een track wordt een afbeelding gemaakt
|
||||||
|
- Niet trillen bij starten van opnemen
|
||||||
|
- Bij opnemen: UI toont recentst ontvangen hoogte, snelheid en richting (ook al is deze niet opgeslagen)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Berekening van totale tijd mag geen pauze-tijd bevatten (vereist opnieuw importeren)
|
||||||
|
- (opnieuw) Gebruik aangepaste overzichten indien aanwezig
|
||||||
|
|
||||||
|
Ontwikkelaar:
|
||||||
|
- Upgrade naar SDK31 en AGP7.0.0 (vereist JDK11)
|
||||||
|
- TrackRecordingService: sensorfusie (TrackPointCreator) en TrackPoint-opslag (TrackRecordingManager) geëxtraheerd
|
||||||
|
- ExportImportTest is nu deterministisch/constant
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Gizliliğinize saygı duyan bir spor takipçisi.
|
||||||
|
|
||||||
|
Spor ve açık hava etkinlikleri mutluluk, beceri ve güven verir.
|
||||||
|
|
||||||
|
Antrenmanınızı takip ederek sağlığınıza değer verin.
|
||||||
|
Koşarken veya yürürken kaydeder ve size bisiklet sürmek için daha büyük ekranlı bir bisiklet bilgisayarı verir.
|
||||||
|
Yol boyunca ilginç yerleri resimlerle işaretleyin.
|
||||||
|
Analiz için kaydedilen istatistikleri çok ayrıntılı tutun.
|
||||||
|
Yalnızca başkalarının sahip olmasını istediğiniz verileri paylaşın.
|
||||||
|
|
||||||
|
* Sesli duyurular.
|
||||||
|
* Bluetooth LE sensörlerini destekler: kalp atış hızı, hız ve mesafe (bisiklet), tempo (bisiklet) ve güç ölçer (bisiklet).
|
||||||
|
* İrtifa kazancı ve kaybı: barometrik sensör aracılığıyla.
|
||||||
|
* EGM2008'de gösterilen rakım (ortalama deniz seviyesinin üzerinde); WGS84 olarak dışa aktarılır.
|
||||||
|
* Verileri KMZ (fotoğraflar dahil), KML veya GPX olarak parça olarak dışa aktarın.
|
||||||
|
* İnternet erişimi veya ekstra izin yok.
|
||||||
|
* Sistem ayarlarına uygun olarak koyu ve açık tema.
|
||||||
|
* Reklamsız.
|
||||||
|
|
||||||
|
Libre yazılımı / Özgür Yazılım / Açık Kaynak
|
||||||
|
yani kaynak kodunu kullanabilir, inceleyebilir, değiştirebilir ve paylaşabilirsiniz.
|
||||||
|
Lisanslı Apache 2.0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Gizliliğinize saygı duyan bir spor takipçisi.
|
||||||
@@ -1,2 +1 @@
|
|||||||
android.enableJetifier=true
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 862 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 1001 KiB After Width: | Height: | Size: 1012 KiB |
@@ -5,6 +5,7 @@ import static androidx.test.espresso.Espresso.onData;
|
|||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.Espresso.pressBack;
|
import static androidx.test.espresso.Espresso.pressBack;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
@@ -14,7 +15,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
|||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.anything;
|
import static org.hamcrest.Matchers.anything;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
|
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
|
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
|
||||||
|
|
||||||
@@ -55,150 +55,33 @@ public class EspressoDeleteTrackTest {
|
|||||||
@Rule
|
@Rule
|
||||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
|
||||||
private MyIdlingResource idlingResource;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void registerIntentServiceIdlingResource() {
|
|
||||||
Instrumentation instrumentation
|
|
||||||
= InstrumentationRegistry.getInstrumentation();
|
|
||||||
idlingResource = new MyIdlingResource(instrumentation.getTargetContext());
|
|
||||||
IdlingRegistry.getInstance().register(idlingResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void unregisterIntentServiceIdlingResource() {
|
|
||||||
IdlingRegistry.getInstance().unregister(idlingResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore("Test fails permanently")
|
|
||||||
@Deprecated
|
|
||||||
@Test
|
@Test
|
||||||
public void espressoDeleteTrackTest() {
|
public void espressoDeleteTrackTest() {
|
||||||
int countBefore;
|
|
||||||
|
|
||||||
{
|
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
onView(withId(R.id.track_list_fab_action)).perform(click());
|
||||||
trackControllerRecordButton.perform(click());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
|
||||||
|
|
||||||
// wait; stay recording
|
// wait; stay recording
|
||||||
trackControllerRecordButton.perform(waitFor(5000));
|
.perform(waitFor(5000))
|
||||||
|
|
||||||
// stop;
|
// stop;
|
||||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
.perform(longClick());
|
||||||
}
|
|
||||||
|
|
||||||
// back
|
// TrackStoppedActivity
|
||||||
pressBack();
|
onView(withId(R.id.finish_button)).perform(click());
|
||||||
|
|
||||||
// get number of items before deleting a track
|
|
||||||
countBefore = numberOfItemsListView();
|
|
||||||
|
|
||||||
// select track
|
// select track
|
||||||
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(5000));
|
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(longClick());
|
||||||
|
|
||||||
// open menu and delete selected track
|
// open menu and delete selected track
|
||||||
ViewInteraction overflowMenuButton = onView(
|
onView(allOf(withContentDescription("More options"), isDisplayed()))
|
||||||
allOf(withContentDescription("More options"),
|
.perform(click());
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withId(R.id.toolbar),
|
|
||||||
2),
|
|
||||||
2),
|
|
||||||
isDisplayed()));
|
|
||||||
overflowMenuButton.perform(click());
|
|
||||||
|
|
||||||
ViewInteraction appCompatTextView = onView(
|
onView(withText("Delete")).perform(click());
|
||||||
allOf(withId(R.id.title), withText("Delete"),
|
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withId(R.id.content),
|
|
||||||
0),
|
|
||||||
0),
|
|
||||||
isDisplayed()));
|
|
||||||
appCompatTextView.perform(click());
|
|
||||||
|
|
||||||
ViewInteraction appCompatButton = onView(
|
onView(withText("OK")).perform(click());
|
||||||
allOf(withId(android.R.id.button1), withText("Yes"),
|
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withId(R.id.buttonPanel),
|
|
||||||
0),
|
|
||||||
3)));
|
|
||||||
appCompatButton.perform(scrollTo(), click());
|
|
||||||
|
|
||||||
// check number of items after delete
|
// tracklist is empty now
|
||||||
onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1)));
|
onView(allOf(withText("Start recording your next adventure here"), isDisplayed()));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int numberOfItemsListView() {
|
|
||||||
final int[] counts = new int[1];
|
|
||||||
onView(withId(R.id.track_list)).check(matches(new TypeSafeMatcher<View>() {
|
|
||||||
@Override
|
|
||||||
public boolean matchesSafely(View view) {
|
|
||||||
ListView listView = (ListView) view;
|
|
||||||
|
|
||||||
counts[0] = listView.getCount();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void describeTo(Description description) {
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return counts[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class MyIdlingResource implements IdlingResource {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private ResourceCallback resourceCallback;
|
|
||||||
|
|
||||||
public MyIdlingResource(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return MyIdlingResource.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback) {
|
|
||||||
this.resourceCallback = resourceCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isIdleNow() {
|
|
||||||
boolean idle = !isDeleteServiceRunning();
|
|
||||||
if (idle && resourceCallback != null) {
|
|
||||||
resourceCallback.onTransitionToIdle();
|
|
||||||
}
|
|
||||||
return idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDeleteServiceRunning() {
|
|
||||||
ActivityManager manager =
|
|
||||||
(ActivityManager) context.getSystemService(
|
|
||||||
Context.ACTIVITY_SERVICE);
|
|
||||||
for (ActivityManager.RunningServiceInfo info :
|
|
||||||
manager.getRunningServices(Integer.MAX_VALUE)) {
|
|
||||||
if (TrackDeleteService.class.getName().equals(
|
|
||||||
info.service.getClassName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,14 @@ import static androidx.test.espresso.Espresso.onView;
|
|||||||
import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu;
|
import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
|
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||||
|
|
||||||
import androidx.test.espresso.ViewInteraction;
|
import androidx.test.espresso.ViewInteraction;
|
||||||
@@ -25,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
|
||||||
import org.hamcrest.core.IsInstanceOf;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -50,16 +45,15 @@ public class EspressoEditTrackRecordingTest {
|
|||||||
public void espressoEditTrackRecordingTest() {
|
public void espressoEditTrackRecordingTest() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
ViewInteraction fabRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||||
trackControllerRecordButton.perform(click());
|
fabRecordButton.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
|
||||||
|
|
||||||
// wait; stay recording
|
// wait; stay recording
|
||||||
trackControllerRecordButton.perform(waitFor(15000));
|
trackControllerStopButton.perform(waitFor(4000));
|
||||||
|
|
||||||
// open menu
|
// open menu
|
||||||
openContextualActionModeOverflowMenu();
|
openContextualActionModeOverflowMenu();
|
||||||
@@ -68,58 +62,24 @@ public class EspressoEditTrackRecordingTest {
|
|||||||
onView(withText(R.string.menu_edit)).perform(click());
|
onView(withText(R.string.menu_edit)).perform(click());
|
||||||
|
|
||||||
// change name for "New Name"
|
// change name for "New Name"
|
||||||
ViewInteraction textInputEditText = onView(
|
ViewInteraction textInputEditText = onView(withId(R.id.track_edit_name));
|
||||||
allOf(withId(R.id.track_edit_name),
|
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
|
||||||
0),
|
|
||||||
1)));
|
|
||||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
||||||
|
|
||||||
ViewInteraction textInputEditText2 = onView(
|
ViewInteraction textInputEditText2 = onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()));
|
||||||
allOf(withId(R.id.track_edit_name), withText("New Name"),
|
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
|
||||||
0),
|
|
||||||
1),
|
|
||||||
isDisplayed()));
|
|
||||||
textInputEditText2.perform(closeSoftKeyboard());
|
textInputEditText2.perform(closeSoftKeyboard());
|
||||||
|
|
||||||
// save edition
|
// save edition
|
||||||
ViewInteraction appCompatButton = onView(
|
ViewInteraction appCompatButton = onView(withId(R.id.track_edit_save));
|
||||||
allOf(withId(R.id.track_edit_save),
|
|
||||||
childAtPosition(
|
|
||||||
childAtPosition(
|
|
||||||
withClassName(is("android.widget.LinearLayout")),
|
|
||||||
3),
|
|
||||||
1),
|
|
||||||
isDisplayed()));
|
|
||||||
appCompatButton.perform(click());
|
appCompatButton.perform(click());
|
||||||
|
|
||||||
//pause
|
|
||||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
|
||||||
|
|
||||||
// wait; stay paused
|
|
||||||
trackControllerRecordButton.perform(waitFor(1000));
|
|
||||||
|
|
||||||
// resume
|
|
||||||
trackControllerRecordButton.perform(click());
|
|
||||||
|
|
||||||
// wait; stay recording
|
|
||||||
trackControllerRecordButton.perform(waitFor(1000));
|
|
||||||
|
|
||||||
// stop;
|
// stop;
|
||||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
trackControllerStopButton.perform(longClick());
|
||||||
|
|
||||||
// check track's name is "New Name"
|
// it's on track stopped activity and there are two buttons
|
||||||
ViewInteraction textView = onView(
|
ViewInteraction resumeButton = onView(withId(R.id.resume_button));
|
||||||
allOf(withId(R.id.stats_name_value),
|
resumeButton.check(matches(isDisplayed()));
|
||||||
withParent(withParent(IsInstanceOf.instanceOf(android.widget.ScrollView.class))),
|
ViewInteraction finishButton = onView(withId(R.id.finish_button));
|
||||||
isDisplayed()));
|
finishButton.check(matches(isDisplayed()));
|
||||||
textView.check(matches(withText("New Name")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,23 @@ package de.dennisguse.opentracks;
|
|||||||
import static androidx.test.espresso.Espresso.onData;
|
import static androidx.test.espresso.Espresso.onData;
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.anything;
|
import static org.hamcrest.Matchers.anything;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex;
|
import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.test.espresso.UiController;
|
|
||||||
import androidx.test.espresso.ViewAction;
|
|
||||||
import androidx.test.espresso.ViewInteraction;
|
import androidx.test.espresso.ViewInteraction;
|
||||||
import androidx.test.espresso.action.MotionEvents;
|
|
||||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
|
||||||
import org.hamcrest.Matcher;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -39,34 +36,30 @@ public class EspressoUITest {
|
|||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
@Test
|
||||||
public void record_pause_resume_stop() {
|
public void record_stop_resume_stop_finish() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||||
trackControllerRecordButton.perform(click());
|
trackControllerRecordButton.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity: wait to record some time and then stop
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
.perform(waitFor(5000))
|
||||||
|
.perform(longClick());
|
||||||
|
|
||||||
// wait; stay recording
|
// TrackStoppedActivity: resume
|
||||||
trackControllerRecordButton.perform(waitFor(5000));
|
onView(allOf(withId(R.id.resume_button), isClickable()))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
//pause
|
// TrackRecordingActivity: wait and then stop
|
||||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
|
.perform(waitFor(5000))
|
||||||
|
.perform(longClick());
|
||||||
|
|
||||||
// wait; stay paused
|
// TrackStoppedActivity
|
||||||
trackControllerRecordButton.perform(waitFor(1000));
|
onView(withId(R.id.finish_button))
|
||||||
|
.perform(click());
|
||||||
// resume
|
|
||||||
trackControllerRecordButton.perform(click());
|
|
||||||
|
|
||||||
// wait; stay recording
|
|
||||||
trackControllerRecordButton.perform(waitFor(1000));
|
|
||||||
|
|
||||||
// stop;
|
|
||||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +68,13 @@ public class EspressoUITest {
|
|||||||
public void record_move_through_tabs() {
|
public void record_move_through_tabs() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||||
trackControllerRecordButton.perform(click());
|
trackControllerRecordButton.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||||
|
|
||||||
tabLayout.perform(selectTabAtIndex(1));
|
tabLayout.perform(selectTabAtIndex(1));
|
||||||
tabLayout.perform(waitFor(1000));
|
tabLayout.perform(waitFor(1000));
|
||||||
@@ -96,7 +89,7 @@ public class EspressoUITest {
|
|||||||
tabLayout.perform(waitFor(1000));
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
// stop
|
// stop
|
||||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
trackControllerStopButton.perform(longClick());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,36 +97,6 @@ public class EspressoUITest {
|
|||||||
@Test
|
@Test
|
||||||
public void selectAndDeleteTrack() {
|
public void selectAndDeleteTrack() {
|
||||||
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
|
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
|
||||||
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(2000));
|
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(longClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
|
||||||
return new ViewAction() {
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return "Perform long touch.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Matcher<View> getConstraints() {
|
|
||||||
return isDisplayed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void perform(UiController uiController, final View view) {
|
|
||||||
// Get view absolute position
|
|
||||||
int[] location = new int[2];
|
|
||||||
view.getLocationOnScreen(location);
|
|
||||||
|
|
||||||
// Offset coordinates by view position
|
|
||||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
|
||||||
|
|
||||||
// Send down event, pause, and send up
|
|
||||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
|
||||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
|
||||||
MotionEvents.sendUp(uiController, down, coordinates);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class ChartPointTest {
|
|||||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, UnitSystem.IMPERIAL);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), false, UnitSystem.IMPERIAL);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||||
@@ -40,7 +40,7 @@ public class ChartPointTest {
|
|||||||
statistics.setTotalDistance(Distance.of(1000));
|
statistics.setTotalDistance(Distance.of(1000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, UnitSystem.METRIC);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), true, UnitSystem.METRIC);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, (long) point.getTimeOrDistance());
|
assertEquals(1, (long) point.getTimeOrDistance());
|
||||||
@@ -50,9 +50,11 @@ public class ChartPointTest {
|
|||||||
public void create_get_altitude_speed_and_pace() {
|
public void create_get_altitude_speed_and_pace() {
|
||||||
// given
|
// given
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||||
|
.setAltitude(Altitude.EGM2008.of(50));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(50, point.getAltitude(), 0.01);
|
assertEquals(50, point.getAltitude(), 0.01);
|
||||||
@@ -64,9 +66,10 @@ public class ChartPointTest {
|
|||||||
public void create_sensorNotAvailable() {
|
public void create_sensorNotAvailable() {
|
||||||
// given
|
// given
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||||
|
.setAltitude(Altitude.EGM2008.of(50));
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNull(point.getHeartRate());
|
assertNull(point.getHeartRate());
|
||||||
@@ -77,15 +80,16 @@ public class ChartPointTest {
|
|||||||
@Test
|
@Test
|
||||||
public void create_sensorAvailable() {
|
public void create_sensorAvailable() {
|
||||||
// given
|
// given
|
||||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint()
|
||||||
trackPoint.setHeartRate(100f);
|
.setAltitude(Altitude.EGM2008.of(50))
|
||||||
trackPoint.setCadence(101f);
|
.setHeartRate(100f)
|
||||||
trackPoint.setPower(102f);
|
.setCadence(101f)
|
||||||
|
.setPower(102f);
|
||||||
|
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), false, UnitSystem.METRIC);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class ChartValueSeriesTest {
|
|||||||
new int[]{1, 100, 1000},
|
new int[]{1, 100, 1000},
|
||||||
R.string.description_altitude_metric,
|
R.string.description_altitude_metric,
|
||||||
R.string.description_altitude_imperial,
|
R.string.description_altitude_imperial,
|
||||||
|
R.string.description_altitude_imperial,
|
||||||
R.color.chart_altitude_fill,
|
R.color.chart_altitude_fill,
|
||||||
R.color.chart_altitude_border,
|
R.color.chart_altitude_border,
|
||||||
15,
|
15,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class ShareContentProviderTest {
|
|||||||
trackIds.add(new Track.Id(3));
|
trackIds.add(new Track.Id(3));
|
||||||
trackIds.add(new Track.Id(5));
|
trackIds.add(new Track.Id(5));
|
||||||
|
|
||||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "filename.suffix", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||||
|
|
||||||
assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
|
assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,6 @@ public class ShareContentProviderTest {
|
|||||||
trackIds.add(new Track.Id(1));
|
trackIds.add(new Track.Id(1));
|
||||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||||
|
|
||||||
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_with_trackdetail_and_sensordata/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
|
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/KML_WITH_TRACKDETAIL_AND_SENSORDATA/1/..%2F..%2F%261%3D1"), shareURIandMIME.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ import org.junit.runner.RunWith;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||||
|
|
||||||
|
//TODO Parametrized tests
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class DistanceFormatterTest {
|
public class DistanceFormatterTest {
|
||||||
|
|
||||||
@@ -42,4 +43,17 @@ public class DistanceFormatterTest {
|
|||||||
// A small number in imperial
|
// A small number in imperial
|
||||||
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFormatDistance_nautical() {
|
||||||
|
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||||
|
.setDecimalCount(2)
|
||||||
|
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||||
|
.build(context);
|
||||||
|
|
||||||
|
// A large number in nautical
|
||||||
|
assertEquals("2.70 NM", formatter.formatDistance(Distance.of(5000)));
|
||||||
|
// A small number in nautical
|
||||||
|
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ import org.junit.runner.RunWith;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||||
|
|
||||||
|
//TODO Use make parametrized tests?
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SpeedFormatterTest {
|
public class SpeedFormatterTest {
|
||||||
|
|
||||||
@@ -39,12 +40,22 @@ public class SpeedFormatterTest {
|
|||||||
.setReportSpeedOrPace(false)
|
.setReportSpeedOrPace(false)
|
||||||
.build(context);
|
.build(context);
|
||||||
|
|
||||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.34)).first);
|
assertEquals("8:02", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.33)).first);
|
|
||||||
|
|
||||||
assertEquals("min/mi", formatter.getSpeedParts(Speed.zero()).second);
|
assertEquals("min/mi", formatter.getSpeedParts(Speed.zero()).second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSpeedParts_pace_nautical() {
|
||||||
|
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||||
|
.setDecimalCount(2)
|
||||||
|
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||||
|
.setReportSpeedOrPace(false)
|
||||||
|
.build(context);
|
||||||
|
|
||||||
|
assertEquals("9:14", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||||
|
assertEquals("min/NM", formatter.getSpeedParts(Speed.zero()).second);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSpeedParts_speed_metric() {
|
public void testGetSpeedParts_speed_metric() {
|
||||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||||
@@ -69,6 +80,18 @@ public class SpeedFormatterTest {
|
|||||||
assertEquals("mph", formatter.getSpeedParts(Speed.zero()).second);
|
assertEquals("mph", formatter.getSpeedParts(Speed.zero()).second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSpeedParts_speed_nautical() {
|
||||||
|
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||||
|
.setDecimalCount(2)
|
||||||
|
.setUnit(UnitSystem.NAUTICAL_IMPERIAL)
|
||||||
|
.setReportSpeedOrPace(true)
|
||||||
|
.build(context);
|
||||||
|
|
||||||
|
assertEquals("6.5", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||||
|
assertEquals("kn", formatter.getSpeedParts(Speed.zero()).second);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFormatSpeed() {
|
public void testFormatSpeed() {
|
||||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ public class TrackFilenameGeneratorTest {
|
|||||||
return Arrays.asList(new String[][]{
|
return Arrays.asList(new String[][]{
|
||||||
{"{uuid}_{name}", "0000fee0_Best Track.gpx"},
|
{"{uuid}_{name}", "0000fee0_Best Track.gpx"},
|
||||||
{"{name}_{uuid}", "Best Track_0000fee0.gpx"},
|
{"{name}_{uuid}", "Best Track_0000fee0.gpx"},
|
||||||
{"{time}_{name}", "2020-02-02T02_02_02Z_Best Track.gpx" },
|
{"{time}_{name}", "02_02_02_Best Track.gpx"},
|
||||||
{ApplicationProvider.getApplicationContext().getString(R.string.export_filename_format_default), "2020-02-02T02_02_02Z_Best Track.gpx" },
|
{"{date}_{name}", "2020-02-02_Best Track.gpx"},
|
||||||
|
{ApplicationProvider.getApplicationContext().getString(R.string.export_filename_format_default), "2020-02-02_02_02_02_Best Track.gpx"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,11 +162,11 @@ public class ExportImportTest {
|
|||||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:06Z");
|
trackPointCreator.setClock("2020-02-02T02:02:06Z");
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||||
service.pauseCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:20Z");
|
trackPointCreator.setClock("2020-02-02T02:02:20Z");
|
||||||
service.resumeCurrentTrack();
|
service.resumeTrack(trackId);
|
||||||
|
|
||||||
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 13, 15, 10, 0);
|
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 13, 15, 10, 0);
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ public class ExportImportTest {
|
|||||||
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:24Z");
|
trackPointCreator.setClock("2020-02-02T02:02:24Z");
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
@@ -492,7 +492,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator) {
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
|
||||||
@Override
|
@Override
|
||||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
* Test that legacy KML/GPX formats can still be imported.
|
* Test that legacy KML/GPX formats can still be imported.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class GPXImportTest {
|
public class GPXTrackImporterTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
@@ -31,7 +31,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
|||||||
* Test that legacy KML/GPX formats can still be imported.
|
* Test that legacy KML/GPX formats can still be imported.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class KMLImportTest {
|
public class KMLTrackImporterTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
@@ -159,7 +159,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
// when
|
// when
|
||||||
String pauseTime = "2020-02-02T02:02:03Z";
|
String pauseTime = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(pauseTime);
|
trackPointCreator.setClock(pauseTime);
|
||||||
service.pauseCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(new TrackStatistics(startTime, pauseTime, 0, 1, 0, 0, 0f, 0f)
|
assertEquals(new TrackStatistics(startTime, pauseTime, 0, 1, 0, 0, 0f, 0f)
|
||||||
@@ -205,7 +205,7 @@ public class TrackRecordingServiceTestRecording {
|
|||||||
|
|
||||||
String pauseTime = "2020-02-02T02:02:03Z";
|
String pauseTime = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(pauseTime);
|
trackPointCreator.setClock(pauseTime);
|
||||||
service.pauseCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:04Z");
|
trackPointCreator.setClock("2020-02-02T02:02:04Z");
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void recording_startPauseStopResume_no_data() throws InterruptedException {
|
public void recording_startStopResume_no_data() throws InterruptedException {
|
||||||
// given
|
// given
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
@@ -153,31 +153,19 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
service.pauseCurrentTrack();
|
service.resumeTrack(trackId);
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertTrue(service.isPaused());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(new RecordingStatus(trackId, true), service.getRecordingStatusObservable().getValue());
|
|
||||||
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
|
||||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); //TODO The GPS is kept in pause, right? Might be a bug.
|
|
||||||
|
|
||||||
|
|
||||||
// when
|
|
||||||
service.resumeCurrentTrack();
|
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
assertFalse(service.isPaused());
|
|
||||||
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
|
|
||||||
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
@@ -188,7 +176,6 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
assertFalse(service.isPaused());
|
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
@@ -200,8 +187,7 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertFalse(service.isPaused());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
|
|
||||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
||||||
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
@@ -209,29 +195,31 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void invalidStateChanges() throws InterruptedException {
|
public void cannotResume_non_existing_track() throws InterruptedException {
|
||||||
// given
|
// given
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
// when
|
|
||||||
service.pauseCurrentTrack();
|
|
||||||
Thread.sleep(2000);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
service.resumeCurrentTrack();
|
service.resumeTrack(new Track.Id(-1));
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
assertFalse(service.isRecording());
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void cannotEnd_without_starting() throws InterruptedException {
|
||||||
|
// given
|
||||||
|
assertFalse(service.isRecording());
|
||||||
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
|
assertEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
@@ -257,8 +245,7 @@ public class TrackRecordingServiceTestStateMachine {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertFalse(service.isPaused());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(new RecordingStatus(trackId, false), service.getRecordingStatusObservable().getValue());
|
|
||||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
||||||
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(TrackRecordingService.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class TrackRecordingServiceTestUtils {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, service.getTrackPointCreator()));
|
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
|
||||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,13 @@ public class VoiceAnnouncementUtilsTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
contentProviderUtils = new ContentProviderUtils(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
PreferencesUtils.setVoiceAnnounceHeartRate(false);
|
|
||||||
|
PreferencesUtils.setVoiceAnnounceLapHeartRate(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceAverageHeartRate(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceTotalDistance(true);
|
||||||
|
PreferencesUtils.setVoiceAnnounceMovingTime(true);
|
||||||
|
PreferencesUtils.setVoiceAnnounceAverageSpeedPace(true);
|
||||||
|
PreferencesUtils.setVoiceAnnounceLapSpeedPace(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -61,6 +67,54 @@ public class VoiceAnnouncementUtilsTest {
|
|||||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Speed 18.4 kilometers per hour.", announcement);
|
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Speed 18.4 kilometers per hour.", announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnouncement_metric_speed_rounding_check() {
|
||||||
|
TrackStatistics stats = new TrackStatistics();
|
||||||
|
stats.setTotalDistance(Distance.of(20000));
|
||||||
|
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||||
|
stats.setMovingTime(Duration.ofHours(1).plusSeconds(1));
|
||||||
|
stats.setMaxSpeed(Speed.of(100));
|
||||||
|
stats.setTotalAltitudeGain(6000f);
|
||||||
|
|
||||||
|
// when
|
||||||
|
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("Total distance 20.00 kilometers. 1 hour 1 second. Speed 20.0 kilometers per hour.", announcement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnouncement_metric_distance_rounding_check() {
|
||||||
|
TrackStatistics stats = new TrackStatistics();
|
||||||
|
stats.setTotalDistance(Distance.of(19999));
|
||||||
|
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||||
|
stats.setMovingTime(Duration.ofHours(1));
|
||||||
|
stats.setMaxSpeed(Speed.of(100));
|
||||||
|
stats.setTotalAltitudeGain(6000f);
|
||||||
|
|
||||||
|
// when
|
||||||
|
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("Total distance 20.00 kilometers. 1 hour. Speed 20.0 kilometers per hour.", announcement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnouncement_metric_distance_rounding_check_two() {
|
||||||
|
TrackStatistics stats = new TrackStatistics();
|
||||||
|
stats.setTotalDistance(Distance.of(19990));
|
||||||
|
stats.setTotalTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||||
|
stats.setMovingTime(Duration.ofHours(1));
|
||||||
|
stats.setMaxSpeed(Speed.of(100));
|
||||||
|
stats.setTotalAltitudeGain(6000f);
|
||||||
|
|
||||||
|
// when
|
||||||
|
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("Total distance 19.99 kilometers. 1 hour. Speed 20.0 kilometers per hour.", announcement);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_withInterval_metric_speed() {
|
public void getAnnouncement_withInterval_metric_speed() {
|
||||||
// given
|
// given
|
||||||
@@ -199,7 +253,8 @@ public class VoiceAnnouncementUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_heart_rate_and_sensor_statistics() {
|
public void getAnnouncement_heart_rate_and_sensor_statistics() {
|
||||||
PreferencesUtils.setVoiceAnnounceHeartRate(true);
|
PreferencesUtils.setVoiceAnnounceAverageHeartRate(true);
|
||||||
|
PreferencesUtils.setVoiceAnnounceLapHeartRate(true);
|
||||||
|
|
||||||
int numberOfPoints = 1000;
|
int numberOfPoints = 1000;
|
||||||
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
@@ -221,4 +276,34 @@ public class VoiceAnnouncementUtilsTest {
|
|||||||
// then
|
// then
|
||||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
|
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAnnouncement_only_lap_heart_rate() {
|
||||||
|
PreferencesUtils.setVoiceAnnounceLapHeartRate(true);
|
||||||
|
PreferencesUtils.setVoiceAnnounceAverageHeartRate(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceTotalDistance(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceMovingTime(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceAverageSpeedPace(false);
|
||||||
|
PreferencesUtils.setVoiceAnnounceLapSpeedPace(false);
|
||||||
|
|
||||||
|
int numberOfPoints = 1000;
|
||||||
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
|
Track.Id trackId = trackWithStats.first;
|
||||||
|
TrackStatistics stats = trackWithStats.second;
|
||||||
|
IntervalStatistics.Interval lastInterval;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
|
||||||
|
intervalStatistics.addTrackPoints(trackPointIterator);
|
||||||
|
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null);
|
||||||
|
|
||||||
|
// when
|
||||||
|
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(" Current heart rate 133 bpm.", announcement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class TrackPointCreatorTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
subject = new TrackPointCreator(locationHandler, server);
|
subject = new TrackPointCreator(locationHandler, server);
|
||||||
subject.start(context);
|
subject.start(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class PreferencesUtilsTest {
|
|||||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, trackFileFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -73,7 +73,7 @@ public class PreferencesUtilsTest {
|
|||||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, trackFileFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
|
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
|
||||||
|
|
||||||
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
||||||
assertEquals(28.75, statistics.getMaxAltitude(), 0.01);
|
assertEquals(32.5, statistics.getMaxAltitude(), 0.01);
|
||||||
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
|
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
|
||||||
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
||||||
|
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ public class EspressoAggregatedFilterTest {
|
|||||||
@Test
|
@Test
|
||||||
public void espressoAggregatedFilterTest() {
|
public void espressoAggregatedFilterTest() {
|
||||||
// open AggregatedStatisticsActivity through toolbar's menu item
|
// open AggregatedStatisticsActivity through toolbar's menu item
|
||||||
onView(ViewMatchers.withId(R.id.toolbar)).check(matches(isDisplayed()));
|
onView(ViewMatchers.withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||||
onView(withId(R.id.toolbar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
|
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
|
||||||
onView(withId(R.id.track_list_aggregated_stats)).perform(click());
|
onView(withId(R.id.track_list_aggregated_stats)).perform(click());
|
||||||
|
|
||||||
// open FilterDialogFragment through toolbar's menu item
|
// open FilterDialogFragment through toolbar's menu item
|
||||||
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
|
onView(withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||||
onView(withId(R.id.toolbar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
||||||
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
|
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
|
||||||
|
|
||||||
// check there's a checkbox with CATEGORY text
|
// check there's a checkbox with CATEGORY text
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package de.dennisguse.opentracks.ui.customRecordingLayout;
|
|||||||
|
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
|
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
|
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ public class EspressoCustomLayoutTest {
|
|||||||
@Test
|
@Test
|
||||||
public void customLayoutTest() {
|
public void customLayoutTest() {
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.controller_record));
|
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.track_list_fab_action));
|
||||||
trackControllerRecordButton.perform(click());
|
trackControllerRecordButton.perform(click());
|
||||||
|
|
||||||
// Get custom layout preferences and check all data fields are showed.
|
// Get custom layout preferences and check all data fields are showed.
|
||||||
@@ -50,8 +50,8 @@ public class EspressoCustomLayoutTest {
|
|||||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
||||||
|
|
||||||
// stop recording
|
// stop recording
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
trackControllerStopButton.perform(longClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
|
||||||
import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal;
|
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
import androidx.test.espresso.PerformException;
|
|
||||||
import androidx.test.espresso.UiController;
|
import androidx.test.espresso.UiController;
|
||||||
import androidx.test.espresso.ViewAction;
|
import androidx.test.espresso.ViewAction;
|
||||||
import androidx.test.espresso.action.MotionEvents;
|
|
||||||
import androidx.test.espresso.util.HumanReadables;
|
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
@@ -23,8 +16,6 @@ import org.hamcrest.Description;
|
|||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.hamcrest.TypeSafeMatcher;
|
import org.hamcrest.TypeSafeMatcher;
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
public class EspressoUtils {
|
public class EspressoUtils {
|
||||||
|
|
||||||
public static Matcher<View> childAtPosition(
|
public static Matcher<View> childAtPosition(
|
||||||
@@ -80,76 +71,6 @@ public class EspressoUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ViewAction veryLongTouch(final int duration_ms) {
|
|
||||||
return new ViewAction() {
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return "Perform long touch.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Matcher<View> getConstraints() {
|
|
||||||
return isDisplayed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void perform(UiController uiController, final View view) {
|
|
||||||
// Get view absolute position
|
|
||||||
int[] location = new int[2];
|
|
||||||
view.getLocationOnScreen(location);
|
|
||||||
|
|
||||||
// Offset coordinates by view position
|
|
||||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
|
||||||
|
|
||||||
// Send down event, pause, and send up
|
|
||||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
|
||||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
|
||||||
MotionEvents.sendUp(uiController, down, coordinates);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ViewAction waitId(final int viewId, final long millis) {
|
|
||||||
return new ViewAction() {
|
|
||||||
@Override
|
|
||||||
public Matcher<View> getConstraints() {
|
|
||||||
return isRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void perform(final UiController uiController, final View view) {
|
|
||||||
uiController.loopMainThreadUntilIdle();
|
|
||||||
final long startTime = System.currentTimeMillis();
|
|
||||||
final long endTime = startTime + millis;
|
|
||||||
final Matcher<View> viewMatcher = withId(viewId);
|
|
||||||
|
|
||||||
do {
|
|
||||||
for (View child : breadthFirstViewTraversal(view)) {
|
|
||||||
// found view with required ID
|
|
||||||
if (viewMatcher.matches(child)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uiController.loopMainThreadForAtLeast(50);
|
|
||||||
}
|
|
||||||
while (System.currentTimeMillis() < endTime);
|
|
||||||
|
|
||||||
// timeout happens
|
|
||||||
throw new PerformException.Builder()
|
|
||||||
.withActionDescription(this.getDescription())
|
|
||||||
.withViewDescription(HumanReadables.describe(view))
|
|
||||||
.withCause(new TimeoutException())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ViewAction selectTabAtIndex(final int index) {
|
public static ViewAction selectTabAtIndex(final int index) {
|
||||||
return new ViewAction() {
|
return new ViewAction() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class ExportUtilsTest extends TestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsExportFileExists_exists() {
|
|
||||||
// given
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
String format = "kmz";
|
|
||||||
List<String> fileNames = new ArrayList<>();
|
|
||||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
|
||||||
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
|
|
||||||
|
|
||||||
// when
|
|
||||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(exists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsExportFileExists_not_exists1() {
|
|
||||||
// given
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
String format = "gpx";
|
|
||||||
List<String> fileNames = new ArrayList<>();
|
|
||||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
|
|
||||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
|
||||||
|
|
||||||
// when
|
|
||||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertFalse(exists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsExportFileExists_not_exists2() {
|
|
||||||
// given
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
String format = "kmz";
|
|
||||||
List<String> fileNames = new ArrayList<>();
|
|
||||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
|
||||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
|
||||||
|
|
||||||
// when
|
|
||||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertFalse(exists);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -77,14 +77,14 @@ limitations under the License.
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- <activity-->
|
<activity
|
||||||
<!-- android:name=".publicapi.StopRecording"-->
|
android:name=".publicapi.StopRecording"
|
||||||
<!-- android:exported="true"-->
|
android:exported="true"
|
||||||
<!-- android:theme="@style/SplashTheme">-->
|
android:theme="@style/SplashTheme">
|
||||||
<!-- <intent-filter>-->
|
<intent-filter>
|
||||||
<!-- <category android:name="android.intent.category.DEFAULT" />-->
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<!-- </intent-filter>-->
|
</intent-filter>
|
||||||
<!-- </activity>-->
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
|
|
||||||
@@ -324,6 +324,7 @@ limitations under the License.
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".TrackRecordedActivity" />
|
<activity android:name=".TrackRecordedActivity" />
|
||||||
|
<activity android:name=".TrackStoppedActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".TrackRecordingActivity"
|
android:name=".TrackRecordingActivity"
|
||||||
@@ -347,21 +348,12 @@ limitations under the License.
|
|||||||
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
|
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".share.ShowOnMapProxyActivity$KML"
|
android:name=".ShowErrorActivity"
|
||||||
android:exported="true">
|
android:excludeFromRecents="true"
|
||||||
<intent-filter android:label="@string/open_track_as_KML">
|
android:exported="false"
|
||||||
<action android:name="Intent.OpenTracks-Dashboard" />
|
android:finishOnTaskLaunch="true"
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
android:launchMode="singleInstance"
|
||||||
</intent-filter>
|
android:process=":crash"/>
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".share.ShowOnMapProxyActivity$GPX"
|
|
||||||
android:exported="true">
|
|
||||||
<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=".data.ShareContentProvider"
|
android:name=".data.ShareContentProvider"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public class AboutActivity extends AbstractActivity {
|
|||||||
viewBinding.aboutTextVersionCode.setText(getString(R.string.about_version_code, SystemUtils.getAppVersionCode(this)));
|
viewBinding.aboutTextVersionCode.setText(getString(R.string.about_version_code, SystemUtils.getAppVersionCode(this)));
|
||||||
viewBinding.aboutAppUrl.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
viewBinding.aboutAppUrl.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
||||||
|
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||||
|
|
||||||
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ package de.dennisguse.opentracks;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement;
|
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement;
|
||||||
|
|
||||||
@@ -38,9 +36,6 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
|
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
|
||||||
|
|
||||||
setContentView(getRootView());
|
setContentView(getRootView());
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
setupActionBarBack(toolbar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,12 +45,4 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract View getRootView();
|
protected abstract View getRootView();
|
||||||
|
|
||||||
protected void setupActionBarBack(@Nullable Toolbar toolbar) {
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
if (toolbar != null) {
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,14 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
|
|||||||
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
||||||
trackDeleteServiceConnection.unbind(this);
|
trackDeleteServiceConnection.unbind(this);
|
||||||
trackDeleteServiceConnection = null;
|
trackDeleteServiceConnection = null;
|
||||||
|
onDeleteFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void onDeleteConfirmed();
|
protected abstract void onDeleteConfirmed();
|
||||||
|
|
||||||
|
protected abstract void onDeleteFinished();
|
||||||
|
|
||||||
protected abstract Track.Id getRecordingTrackId();
|
protected abstract Track.Id getRecordingTrackId();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.TransitionDrawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
|
|
||||||
import de.dennisguse.opentracks.services.RecordingData;
|
|
||||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|
||||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track controller for record, pause, resume, and stop.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
//TODO Move buttons into separate components
|
|
||||||
public class ControllerFragment extends Fragment implements View.OnTouchListener {
|
|
||||||
|
|
||||||
private static final String TAG = ControllerFragment.class.getSimpleName();
|
|
||||||
|
|
||||||
private Handler handlerUpdateTotalTime;
|
|
||||||
|
|
||||||
private Runnable buttonDelay;
|
|
||||||
private int buttonDelayDuration;
|
|
||||||
|
|
||||||
private ControllerFragmentBinding viewBinding;
|
|
||||||
private TransitionDrawable transition;
|
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
|
||||||
private RecordingStatus recordingStatus;
|
|
||||||
private RecordingData recordingData;
|
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
|
||||||
service.getRecordingStatusObservable()
|
|
||||||
.observe(ControllerFragment.this, this::onRecordingStatusChanged);
|
|
||||||
service.getRecordingDataObservable()
|
|
||||||
.observe(ControllerFragment.this, this::onTotalTimeChanged);
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
buttonDelayDuration = getResources().getInteger(R.integer.buttonDelayMillis);
|
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
|
||||||
handlerUpdateTotalTime = new Handler();
|
|
||||||
recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
viewBinding = ControllerFragmentBinding.inflate(inflater, container, false);
|
|
||||||
|
|
||||||
viewBinding.controllerRecord.setOnTouchListener(this);
|
|
||||||
viewBinding.controllerRecord.setOnClickListener((view) -> {
|
|
||||||
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Callback callback = (Callback) getContext();
|
|
||||||
if (callback != null) {
|
|
||||||
callback.recordStart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewBinding.controllerStop.setOnTouchListener(this);
|
|
||||||
transition = (TransitionDrawable) viewBinding.controllerContainer.getBackground();
|
|
||||||
|
|
||||||
onRecordingStatusChanged(recordingStatus);
|
|
||||||
|
|
||||||
return viewBinding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
trackRecordingServiceConnection.startConnection(getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
handlerUpdateTotalTime.removeCallbacksAndMessages(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
trackRecordingServiceConnection.unbind(getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
viewBinding = null;
|
|
||||||
transition = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
trackRecordingServiceConnection = null;
|
|
||||||
handlerUpdateTotalTime = null;
|
|
||||||
recordingStatus = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO Move into a separate object
|
|
||||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(final View view, MotionEvent event) {
|
|
||||||
if (!isResumed()) {
|
|
||||||
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
|
||||||
buttonDelay = null;
|
|
||||||
view.setPressed(false);
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
|
|
||||||
showStatusSetDefaultText();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Trigger button pressed animation
|
|
||||||
view.setPressed(true);
|
|
||||||
|
|
||||||
//To pause a recording
|
|
||||||
if (viewBinding.controllerRecord.equals(view)
|
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
|
||||||
&& recordingStatus.isRecordingAndNotPaused()) {
|
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(getContext(), 150);
|
|
||||||
viewBinding.controllerStatus.setText(R.string.hold_to_pause);
|
|
||||||
|
|
||||||
buttonDelay = () -> {
|
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
|
||||||
view.performClick();
|
|
||||||
Callback callback = (Callback) getContext();
|
|
||||||
if (callback != null) {
|
|
||||||
callback.recordPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(getContext(), 1000);
|
|
||||||
};
|
|
||||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//To stop a recording
|
|
||||||
if (viewBinding.controllerStop.equals(view)
|
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
|
||||||
&& recordingStatus.isRecording()) {
|
|
||||||
|
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(getContext(), 150);
|
|
||||||
viewBinding.controllerStatus.setText(R.string.hold_to_stop);
|
|
||||||
|
|
||||||
buttonDelay = () -> {
|
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
|
||||||
Context context = getContext();
|
|
||||||
Callback callback = (Callback) context;
|
|
||||||
if (callback != null) {
|
|
||||||
callback.recordStop();
|
|
||||||
ActivityUtils.vibrate(context, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
};
|
|
||||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hide() {
|
|
||||||
viewBinding.controllerContainer.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
|
||||||
viewBinding.controllerContainer.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showStatusSetDefaultText() {
|
|
||||||
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
void recordStart();
|
|
||||||
|
|
||||||
void recordPause();
|
|
||||||
|
|
||||||
void recordStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
|
||||||
recordingStatus = status;
|
|
||||||
|
|
||||||
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
|
|
||||||
viewBinding.controllerRecord.setContentDescription(getContext().getString(recordingStatus.isRecordingAndNotPaused() ? R.string.image_pause : R.string.image_record));
|
|
||||||
|
|
||||||
viewBinding.controllerStop.setEnabled(recordingStatus.isRecording());
|
|
||||||
|
|
||||||
viewBinding.controllerStatus.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
if (recordingStatus.isRecording()) {
|
|
||||||
viewBinding.controllerStatus.setTextColor(getContext().getResources().getColor(recordingStatus.isPaused() ? android.R.color.white : R.color.recording_text));
|
|
||||||
showStatusSetDefaultText();
|
|
||||||
}
|
|
||||||
|
|
||||||
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTotalTimeChanged(RecordingData recordingData) {
|
|
||||||
this.recordingData = recordingData;
|
|
||||||
|
|
||||||
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(this.recordingData.getTrackStatistics().getTotalTime()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,14 +10,18 @@ import de.dennisguse.opentracks.ui.util.ViewUtils;
|
|||||||
|
|
||||||
public class HelpActivity extends AbstractActivity {
|
public class HelpActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
private HelpBinding helpBinding;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setSupportActionBar(helpBinding.bottomAppBarLayout.bottomAppBar);
|
||||||
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getRootView() {
|
protected View getRootView() {
|
||||||
return HelpBinding.inflate(getLayoutInflater()).getRoot();
|
helpBinding = HelpBinding.inflate(getLayoutInflater());
|
||||||
|
return helpBinding.getRoot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
|
||||||
|
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||||
|
|
||||||
|
public class ShowErrorActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
public static final String EXTRA_ERROR_TEXT = "error";
|
||||||
|
|
||||||
|
private ActivityShowErrorBinding viewBinding;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
viewBinding.textViewError.setText(getIntent().getStringExtra(EXTRA_ERROR_TEXT));
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(createErrorTitle());
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getRootView() {
|
||||||
|
viewBinding = ActivityShowErrorBinding.inflate(getLayoutInflater());
|
||||||
|
return viewBinding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createErrorTitle() {
|
||||||
|
return String.format(getString(R.string.error_crash_title), getString(R.string.app_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reportBug() {
|
||||||
|
Uri uriUrl;
|
||||||
|
try {
|
||||||
|
uriUrl = Uri.parse(
|
||||||
|
String.format(
|
||||||
|
getString(R.string.report_issue_link),
|
||||||
|
URLEncoder.encode(viewBinding.textViewError.getText().toString(), StandardCharsets.UTF_8.toString())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (final UnsupportedEncodingException ignored) {
|
||||||
|
// can't happen as UTF-8 is always available
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.show_error, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.error_share) {
|
||||||
|
onClickedShare();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.error_report) {
|
||||||
|
reportBug();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickedShare() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, createErrorTitle());
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, viewBinding.textViewError.getText());
|
||||||
|
intent.setType("text/plain");
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.ExceptionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code that is executed when the application starts.
|
* Code that is executed when the application starts.
|
||||||
@@ -37,6 +43,36 @@ public class Startup extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(final Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
|
||||||
|
// handle crashes only outside the crash reporter activity/process
|
||||||
|
if (!isCrashReportingProcess()) {
|
||||||
|
Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
ExceptionHandler crashReporter = new ExceptionHandler(this, defaultPlatformHandler);
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(crashReporter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCrashReportingProcess() {
|
||||||
|
String processName = "";
|
||||||
|
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
|
// Using the same technique as Application.getProcessName() for older devices
|
||||||
|
// Using reflection since ActivityThread is an internal API
|
||||||
|
try {
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
Class<?> activityThread = Class.forName("android.app.ActivityThread");
|
||||||
|
@SuppressLint("DiscouragedPrivateApi") Method getProcessName = activityThread.getDeclaredMethod("currentProcessName");
|
||||||
|
processName = (String) getProcessName.invoke(null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
processName = Application.getProcessName();
|
||||||
|
}
|
||||||
|
return processName != null && processName.endsWith(":crash");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the database used by SQLiteOpenHelper.
|
* Returns the name of the database used by SQLiteOpenHelper.
|
||||||
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
|
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
|
||||||
|
|||||||
@@ -71,17 +71,17 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewBinding.trackEditName.setText(track.getName());
|
viewBinding.fields.trackEditName.setText(track.getName());
|
||||||
|
|
||||||
viewBinding.trackEditActivityType.setText(track.getCategory());
|
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
||||||
|
|
||||||
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);
|
||||||
viewBinding.trackEditActivityType.setAdapter(adapter);
|
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
||||||
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
||||||
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||||
TrackEditActivity.this, viewBinding.trackEditActivityType.getText().toString()));
|
TrackEditActivity.this, viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,22 +94,23 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
}
|
}
|
||||||
|
|
||||||
setActivityTypeIcon(iconValue);
|
setActivityTypeIcon(iconValue);
|
||||||
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.trackEditActivityType.getText().toString()));
|
viewBinding.fields.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||||
|
|
||||||
viewBinding.trackEditDescription.setText(track.getDescription());
|
viewBinding.fields.trackEditDescription.setText(track.getDescription());
|
||||||
|
|
||||||
viewBinding.trackEditSave.setOnClickListener(v -> {
|
viewBinding.trackEditSave.setOnClickListener(v -> {
|
||||||
TrackUtils.updateTrack(TrackEditActivity.this, track, viewBinding.trackEditName.getText().toString(),
|
TrackUtils.updateTrack(TrackEditActivity.this, track, viewBinding.fields.trackEditName.getText().toString(),
|
||||||
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
|
viewBinding.fields.trackEditActivityType.getText().toString(), viewBinding.fields.trackEditDescription.getText().toString(),
|
||||||
contentProviderUtils);
|
contentProviderUtils);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
setTitle(R.string.menu_edit);
|
|
||||||
viewBinding.trackEditCancel.setOnClickListener(v -> finish());
|
viewBinding.trackEditCancel.setOnClickListener(v -> finish());
|
||||||
viewBinding.trackEditCancel.setVisibility(View.VISIBLE);
|
viewBinding.trackEditCancel.setVisibility(View.VISIBLE);
|
||||||
}
|
|
||||||
|
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_edit));
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
@@ -137,12 +138,12 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
|||||||
|
|
||||||
private void setActivityTypeIcon(String iconValue) {
|
private void setActivityTypeIcon(String iconValue) {
|
||||||
this.iconValue = iconValue;
|
this.iconValue = iconValue;
|
||||||
viewBinding.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
viewBinding.fields.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChooseActivityTypeDone(String value) {
|
public void onChooseActivityTypeDone(String value) {
|
||||||
setActivityTypeIcon(value);
|
setActivityTypeIcon(value);
|
||||||
viewBinding.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
|
viewBinding.fields.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,19 @@ import android.database.Cursor;
|
|||||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Vibrator;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
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.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.CursorLoader;
|
import androidx.loader.content.CursorLoader;
|
||||||
@@ -68,6 +70,7 @@ import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
|||||||
import de.dennisguse.opentracks.ui.util.ListItemUtils;
|
import de.dennisguse.opentracks.ui.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PermissionUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
@@ -76,7 +79,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, ControllerFragment.Callback {
|
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||||
|
|
||||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -101,6 +104,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||||
boolean isSingleSelection = trackIds.length == 1;
|
boolean isSingleSelection = trackIds.length == 1;
|
||||||
|
|
||||||
|
viewBinding.bottomAppBar.performHide(true);
|
||||||
|
viewBinding.trackListFabAction.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,12 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
public boolean onClick(int itemId, int[] positions, long[] trackIds) {
|
public boolean onClick(int itemId, int[] positions, long[] trackIds) {
|
||||||
return handleContextItem(itemId, trackIds);
|
return handleContextItem(itemId, trackIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
viewBinding.trackListFabAction.setVisibility(View.VISIBLE);
|
||||||
|
viewBinding.bottomAppBar.performShow(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
@@ -127,7 +139,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
private MenuItem startGpsMenuItem;
|
private MenuItem startGpsMenuItem;
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||||
service.getRecordingStatusObservable()
|
service.getRecordingStatusObservable()
|
||||||
.observe(TrackListActivity.this, this::onRecordingStatusChanged);
|
.observe(TrackListActivity.this, this::onRecordingStatusChanged);
|
||||||
|
|
||||||
@@ -152,19 +164,22 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
|
requestRequiredPermissions();
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
|
|
||||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackIdId) -> {
|
||||||
if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().getId()) {
|
Track.Id trackId = new Track.Id(trackIdId);
|
||||||
|
if (recordingStatus.isRecording() && trackId.equals(recordingStatus.getTrackId())) {
|
||||||
// Is recording -> open record activity.
|
// Is recording -> open record activity.
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
} else {
|
} else {
|
||||||
// Not recording -> open detail activity.
|
// Not recording -> open detail activity.
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||||
this,
|
this,
|
||||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||||
@@ -201,7 +216,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||||
String description = cursor.getString(descriptionIndex);
|
String description = cursor.getString(descriptionIndex);
|
||||||
|
|
||||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
|
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording,
|
||||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
||||||
category, description, false);
|
category, description, false);
|
||||||
@@ -210,13 +225,51 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||||
ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback);
|
ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback);
|
||||||
|
|
||||||
|
viewBinding.trackListFabAction.setOnClickListener((view) -> {
|
||||||
|
if (recordingStatus.isRecording()) {
|
||||||
|
Toast.makeText(TrackListActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not Recording -> Recording
|
||||||
|
updateGpsMenuItem(false, true);
|
||||||
|
new TrackRecordingServiceConnection((service, connection) -> {
|
||||||
|
Track.Id trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||||
|
newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(newIntent);
|
||||||
|
|
||||||
|
connection.unbind(this);
|
||||||
|
}).startAndBind(this);
|
||||||
|
});
|
||||||
|
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
|
||||||
|
if (!recordingStatus.isRecording()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recording -> Stop
|
||||||
|
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||||
|
updateGpsMenuItem(false, false);
|
||||||
|
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||||
|
viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24);
|
||||||
|
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBar);
|
||||||
|
|
||||||
loadData(getIntent());
|
loadData(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void requestRequiredPermissions() {
|
||||||
protected void setupActionBarBack(Toolbar toolbar) {
|
if (!PermissionUtils.hasGPSPermission(this)) {
|
||||||
setSupportActionBar(toolbar);
|
PermissionUtils.requestGPSPermission(this, null, () -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
|
||||||
toolbar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
}
|
||||||
|
|
||||||
|
if (!PermissionUtils.hasBluetoothPermissions(this)) {
|
||||||
|
PermissionUtils.requestBluetoothPermission(this, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -234,6 +287,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
// Update UI
|
// Update UI
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||||
|
|
||||||
|
// Float button
|
||||||
|
setFloatButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -263,6 +319,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||||
|
|
||||||
|
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
|
||||||
|
|
||||||
|
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
|
||||||
|
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white));
|
||||||
|
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
|
||||||
|
|
||||||
searchView.setOnCloseListener(() -> {
|
searchView.setOnCloseListener(() -> {
|
||||||
searchView.clearFocus();
|
searchView.clearFocus();
|
||||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
@@ -382,6 +445,11 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDeleteFinished() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected Track.Id getRecordingTrackId() {
|
protected Track.Id getRecordingTrackId() {
|
||||||
@@ -422,7 +490,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (itemId == R.id.list_context_menu_show_on_map) {
|
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||||
IntentDashboardUtils.startDashboard(this, false, trackIds);
|
IntentDashboardUtils.showTrackOnMap(this, false, trackIds);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,38 +583,18 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordStart() {
|
|
||||||
if (recordingStatus.getTrackId() == null) {
|
|
||||||
// Not recording -> Recording
|
|
||||||
updateGpsMenuItem(false, true);
|
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
|
||||||
startActivity(newIntent);
|
|
||||||
} else if (recordingStatus.isPaused()) {
|
|
||||||
// Paused -> Resume
|
|
||||||
updateGpsMenuItem(false, true);
|
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordPause() {
|
|
||||||
updateGpsMenuItem(false, true);
|
|
||||||
trackRecordingServiceConnection.pauseTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordStop() {
|
|
||||||
updateGpsMenuItem(false, false);
|
|
||||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
||||||
gpsStatusValue = newStatus;
|
gpsStatusValue = newStatus;
|
||||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setFloatButton() {
|
||||||
|
viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24);
|
||||||
|
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks_secondary_color : R.color.red_dark));
|
||||||
|
}
|
||||||
|
|
||||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||||
recordingStatus = status;
|
recordingStatus = status;
|
||||||
|
setFloatButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback bindCallback = service -> service.getRecordingStatusObservable()
|
private final TrackRecordingServiceConnection.Callback bindCallback = (service, unused) -> service.getRecordingStatusObservable()
|
||||||
.observe(TrackRecordedActivity.this, this::onRecordingStatusChanged);
|
.observe(TrackRecordedActivity.this, this::onRecordingStatusChanged);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,6 +102,11 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||||
|
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(track != null ? track.getName() : "");
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBar.replaceMenu(R.menu.track_detail);
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||||
|
|
||||||
postponeEnterTransition();
|
postponeEnterTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +167,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
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_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
|
menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
|
||||||
setTitle(track != null ? track.getName() : "");
|
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +179,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||||
IntentDashboardUtils.startDashboard(this, false, trackId);
|
IntentDashboardUtils.showTrackOnMap(this, false, trackId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,11 +203,17 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.getItemId() == R.id.track_detail_resume_track) {
|
if (item.getItemId() == R.id.track_detail_resume_track) {
|
||||||
|
new TrackRecordingServiceConnection((service, connection) -> {
|
||||||
|
service.resumeTrack(trackId);
|
||||||
|
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
|
||||||
|
connection.unbind(this);
|
||||||
finish();
|
finish();
|
||||||
|
}).startAndBind(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +244,11 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
runOnUiThread(this::finish);
|
runOnUiThread(this::finish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDeleteFinished() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link TrackDataHub}.
|
* Gets the {@link TrackDataHub}.
|
||||||
*/
|
*/
|
||||||
@@ -246,7 +260,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
|
trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
|
||||||
if (trackId == null) {
|
if (trackId == null) {
|
||||||
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
|
Log.e(TAG, TrackRecordedActivity.class.getSimpleName() + " needs EXTRA_TRACK_ID.");
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Vibrator;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
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.WindowInsets;
|
||||||
|
import android.view.WindowInsetsController;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
@@ -54,9 +59,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface {
|
||||||
//This mode of communication should be removed anyhow.
|
|
||||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface, ControllerFragment.Callback {
|
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
|
||||||
@@ -73,13 +76,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
private TrackRecordingBinding viewBinding;
|
private TrackRecordingBinding viewBinding;
|
||||||
|
|
||||||
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
|
||||||
@Deprecated
|
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
|
|
||||||
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||||
service.getRecordingStatusObservable()
|
service.getRecordingStatusObservable()
|
||||||
.observe(TrackRecordingActivity.this, this::onRecordingStatusChanged);
|
.observe(TrackRecordingActivity.this, this::onRecordingStatusChanged);
|
||||||
|
|
||||||
@@ -87,18 +88,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
|
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
|
||||||
|
|
||||||
if (!service.isRecording()) {
|
if (!service.isRecording()) {
|
||||||
if (trackId == null) {
|
finish();
|
||||||
// trackId isn't initialized -> leads a new recording.
|
return;
|
||||||
trackId = service.startNewTrack();
|
|
||||||
} else {
|
|
||||||
// trackId is initialized -> resumes the track.
|
|
||||||
service.resumeTrack(trackId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A recording track is on.
|
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackDataHub.setRecordingStatus(recordingStatus);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
@@ -116,24 +111,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary
|
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary
|
||||||
};
|
};
|
||||||
|
|
||||||
private MenuItem insertMarkerMenuItem;
|
|
||||||
private MenuItem markerListMenuItem;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
trackId = null;
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
//Activity was recreated.
|
|
||||||
trackId = savedInstanceState.getParcelable(EXTRA_TRACK_ID);
|
|
||||||
} else {
|
|
||||||
// Resume a track
|
|
||||||
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||||
if (trackId != null && contentProviderUtils.getTrack(trackId) == null) {
|
if (trackId == null) {
|
||||||
Log.w(TAG, "TrackId does not exists; cannot continue the recording.");
|
throw new RuntimeException("TrackId is mandatory");
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
if (contentProviderUtils.getTrack(trackId) == null) {
|
||||||
|
Log.w(TAG, "TrackId does not exists.");
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
@@ -147,7 +136,25 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
requestGPSPermissions();
|
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
|
||||||
|
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
|
||||||
|
viewBinding.trackRecordingFabAction.setBackgroundColor(getResources().getColor(R.color.opentracks_secondary_color));
|
||||||
|
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
|
||||||
|
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||||
|
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||||
|
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackStoppedActivity.class)
|
||||||
|
.putExtra(TrackStoppedActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(newIntent);
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
viewBinding.trackRecordingFabAction.setOnClickListener((view) -> {
|
||||||
|
Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewBinding.bottomAppBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24);
|
||||||
|
setSupportActionBar(viewBinding.bottomAppBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -181,9 +188,20 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setFullscreenPolicy() {
|
private void setFullscreenPolicy() {
|
||||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen();
|
boolean fullscreen = PreferencesUtils.shouldUseFullscreen();
|
||||||
|
|
||||||
if (keepScreenOn) {
|
WindowInsetsControllerCompat windowInsetsController = ViewCompat.getWindowInsetsController(getWindow().getDecorView());
|
||||||
|
if (windowInsetsController != null) {
|
||||||
|
windowInsetsController.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||||
|
if (fullscreen) {
|
||||||
|
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
|
||||||
|
} else {
|
||||||
|
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullscreen) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
} else {
|
} else {
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
@@ -220,7 +238,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt(CURRENT_TAB_TAG_KEY, viewBinding.trackDetailActivityViewPager.getCurrentItem());
|
outState.putInt(CURRENT_TAB_TAG_KEY, viewBinding.trackDetailActivityViewPager.getCurrentItem());
|
||||||
outState.putParcelable(EXTRA_TRACK_ID, trackId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -247,23 +264,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.track_record, menu);
|
getMenuInflater().inflate(R.menu.track_record, menu);
|
||||||
|
|
||||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
|
||||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
||||||
updateMenuItems(recordingStatus.isPaused());
|
|
||||||
return super.onPrepareOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
IntentDashboardUtils.showTrackOnMap(this, true, trackId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,15 +322,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
return trackDataHub;
|
return trackDataHub;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the menu items.
|
|
||||||
*/
|
|
||||||
private void updateMenuItems(boolean isPaused) {
|
|
||||||
insertMarkerMenuItem.setVisible(!isPaused);
|
|
||||||
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
|
||||||
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChooseActivityTypeDone(String iconValue) {
|
public void onChooseActivityTypeDone(String iconValue) {
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
@@ -331,29 +329,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordStart() {
|
|
||||||
updateMenuItems(false);
|
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordPause() {
|
|
||||||
updateMenuItems(true);
|
|
||||||
trackRecordingServiceConnection.pauseTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordStop() {
|
|
||||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
|
||||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
|
||||||
startActivity(newIntent);
|
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
|
||||||
updateMenuItems(true);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
||||||
@@ -399,6 +374,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||||
|
if (!status.isRecording()) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
recordingStatus = status;
|
recordingStatus = status;
|
||||||
|
|
||||||
trackDataHub.setRecordingStatus(recordingStatus);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
@@ -407,24 +385,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestGPSPermissions() {
|
|
||||||
if (PermissionUtils.hasGPSPermission(this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
|
|
||||||
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
|
||||||
Boolean coarseLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
|
|
||||||
if (fineLocationGranted == null || !fineLocationGranted
|
|
||||||
|| coarseLocationGranted == null || !coarseLocationGranted) {
|
|
||||||
Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
|
|
||||||
locationPermissionRequest.launch(permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
||||||
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
|
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
|
||||||
snackbar.dismiss();
|
snackbar.dismiss();
|
||||||
@@ -437,7 +397,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
.make(viewBinding.trackRecordingCoordinatorLayout,
|
.make(viewBinding.trackRecordingCoordinatorLayout,
|
||||||
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
||||||
Snackbar.LENGTH_INDEFINITE)
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(getString(R.string.generic_dismiss), v -> {});
|
.setAction(getString(R.string.generic_dismiss), v -> {
|
||||||
|
});
|
||||||
snackbar.show();
|
snackbar.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||||
|
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
import de.dennisguse.opentracks.databinding.TrackStoppedBinding;
|
||||||
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.util.ExportUtils;
|
||||||
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
|
public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
|
||||||
|
|
||||||
|
private static final String TAG = TrackStoppedActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
|
||||||
|
private TrackStoppedBinding viewBinding;
|
||||||
|
|
||||||
|
private Track.Id trackId;
|
||||||
|
|
||||||
|
private boolean isDiscarding = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||||
|
if (trackId == null) {
|
||||||
|
Log.e(TAG, "TrackStoppedActivity needs EXTRA_TRACK_ID.");
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
|
viewBinding.trackEditName.setText(track.getName());
|
||||||
|
|
||||||
|
viewBinding.trackEditActivityType.setText(track.getCategory());
|
||||||
|
|
||||||
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
|
viewBinding.trackEditActivityType.setAdapter(adapter);
|
||||||
|
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
||||||
|
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (!hasFocus) {
|
||||||
|
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||||
|
TrackStoppedActivity.this, viewBinding.trackEditActivityType.getText().toString()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String iconValue = track.getIcon();
|
||||||
|
|
||||||
|
setActivityTypeIcon(iconValue);
|
||||||
|
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.trackEditActivityType.getText().toString()));
|
||||||
|
|
||||||
|
viewBinding.trackEditDescription.setText(track.getDescription());
|
||||||
|
|
||||||
|
viewBinding.time.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = SpeedFormatter.Builder()
|
||||||
|
.setUnit(PreferencesUtils.getUnitSystem())
|
||||||
|
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track.getCategory()))
|
||||||
|
.build(this)
|
||||||
|
.getSpeedParts(track.getTrackStatistics().getAverageMovingSpeed());
|
||||||
|
viewBinding.speed.setText(parts.first);
|
||||||
|
viewBinding.speedUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Pair<String, String> parts = DistanceFormatter.Builder()
|
||||||
|
.setUnit(PreferencesUtils.getUnitSystem())
|
||||||
|
.build(this)
|
||||||
|
.getDistanceParts(track.getTrackStatistics().getTotalDistance());
|
||||||
|
viewBinding.distance.setText(parts.first);
|
||||||
|
viewBinding.distanceUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.finishButton.setOnClickListener(v -> {
|
||||||
|
TrackUtils.updateTrack(TrackStoppedActivity.this, track, viewBinding.trackEditName.getText().toString(),
|
||||||
|
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
|
||||||
|
contentProviderUtils);
|
||||||
|
ExportUtils.postWorkoutExport(this, trackId);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewBinding.resumeButton.setOnClickListener(v -> resumeTrackAndFinish());
|
||||||
|
|
||||||
|
viewBinding.discardButton.setOnClickListener(v -> ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (isDiscarding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onBackPressed();
|
||||||
|
resumeTrackAndFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getRootView() {
|
||||||
|
viewBinding = TrackStoppedBinding.inflate(getLayoutInflater());
|
||||||
|
return viewBinding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActivityTypeIcon(String iconValue) {
|
||||||
|
viewBinding.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChooseActivityTypeDone(String iconValue) {
|
||||||
|
setActivityTypeIcon(iconValue);
|
||||||
|
viewBinding.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(iconValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resumeTrackAndFinish() {
|
||||||
|
new TrackRecordingServiceConnection((service, connection) -> {
|
||||||
|
service.resumeTrack(trackId);
|
||||||
|
|
||||||
|
Intent newIntent = IntentUtils.newIntent(TrackStoppedActivity.this, TrackRecordingActivity.class)
|
||||||
|
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||||
|
startActivity(newIntent);
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||||
|
|
||||||
|
connection.unbind(this);
|
||||||
|
finish();
|
||||||
|
}).startAndBind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDeleteConfirmed() {
|
||||||
|
isDiscarding = true;
|
||||||
|
viewBinding.loadingLayout.loadingText.setText(getString(R.string.track_discarding));
|
||||||
|
viewBinding.contentLinearLayout.setVisibility(View.GONE);
|
||||||
|
viewBinding.loadingLayout.loadingIndeterminate.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDeleteFinished() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Track.Id getRecordingTrackId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,9 +188,9 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, Altitude smoothedAltitude) {
|
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude, chartByDistance, viewBinding.chartView.getUnitSystem());
|
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, chartByDistance, viewBinding.chartView.getUnitSystem());
|
||||||
pendingPoints.add(point);
|
pendingPoints.add(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ public class ChartPoint {
|
|||||||
this.altitude = altitude;
|
this.altitude = altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, Altitude smoothedAltitude, boolean chartByDistance, UnitSystem unitSystem) {
|
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, boolean chartByDistance, UnitSystem unitSystem) {
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(unitSystem);
|
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(unitSystem);
|
||||||
} else {
|
} else {
|
||||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smoothedAltitude != null) {
|
if (trackPoint.hasAltitude()) {
|
||||||
altitude = Distance.of(smoothedAltitude.toM()).toM_FT(unitSystem);
|
altitude = Distance.of(trackPoint.getAltitude().toM()).toM_FT(unitSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smoothedSpeed != null) {
|
if (smoothedSpeed != null) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ abstract class ChartValueSeries {
|
|||||||
private final int[] intervalValues;
|
private final int[] intervalValues;
|
||||||
private final int metricTitleId;
|
private final int metricTitleId;
|
||||||
private final int imperialTitleId;
|
private final int imperialTitleId;
|
||||||
|
private final int nauticalTitleId;
|
||||||
private final Paint fillPaint;
|
private final Paint fillPaint;
|
||||||
private final Paint strokePaint;
|
private final Paint strokePaint;
|
||||||
private final Paint titlePaint;
|
private final Paint titlePaint;
|
||||||
@@ -57,24 +58,13 @@ abstract class ChartValueSeries {
|
|||||||
private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS;
|
private int maxMarkerValue = interval * ChartView.Y_AXIS_INTERVALS;
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
/**
|
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int nauticalTitleId, int fillColor, int strokeColor, int fontSizeSmall, int fontSizeMedium) {
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param absoluteMin the absolute min value
|
|
||||||
* @param absoluteMax the absolute max value
|
|
||||||
* @param intervalValues the list of interval values
|
|
||||||
* @param metricTitleId the metric title id
|
|
||||||
* @param imperialTitleId the imperial title id
|
|
||||||
* @param fillColor the fill color
|
|
||||||
* @param strokeColor the stroke color
|
|
||||||
*/
|
|
||||||
ChartValueSeries(Context context, int absoluteMin, int absoluteMax, int[] intervalValues, int metricTitleId, int imperialTitleId, int fillColor, int strokeColor, int fontSizeSmall, int fontSizeMedium) {
|
|
||||||
this.absoluteMin = absoluteMin;
|
this.absoluteMin = absoluteMin;
|
||||||
this.absoluteMax = absoluteMax;
|
this.absoluteMax = absoluteMax;
|
||||||
this.intervalValues = intervalValues;
|
this.intervalValues = intervalValues;
|
||||||
this.metricTitleId = metricTitleId;
|
this.metricTitleId = metricTitleId;
|
||||||
this.imperialTitleId = imperialTitleId;
|
this.imperialTitleId = imperialTitleId;
|
||||||
|
this.nauticalTitleId = nauticalTitleId;
|
||||||
|
|
||||||
fillPaint = new Paint();
|
fillPaint = new Paint();
|
||||||
fillPaint.setStyle(Style.FILL);
|
fillPaint.setStyle(Style.FILL);
|
||||||
@@ -224,39 +214,27 @@ abstract class ChartValueSeries {
|
|||||||
return metricTitleId;
|
return metricTitleId;
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return imperialTitleId;
|
return imperialTitleId;
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
return nauticalTitleId;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the title paint.
|
|
||||||
*/
|
|
||||||
Paint getTitlePaint() {
|
Paint getTitlePaint() {
|
||||||
return titlePaint;
|
return titlePaint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the marker paint.
|
|
||||||
*/
|
|
||||||
Paint getMarkerPaint() {
|
Paint getMarkerPaint() {
|
||||||
return markerPaint;
|
return markerPaint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the largest marker.
|
|
||||||
*/
|
|
||||||
String getLargestMarker() {
|
String getLargestMarker() {
|
||||||
String minMarker = numberFormat.format(getMinMarkerValue());
|
String minMarker = numberFormat.format(getMinMarkerValue());
|
||||||
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
String maxMarker = numberFormat.format(getMaxMarkerValue());
|
||||||
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
return minMarker.length() >= maxMarker.length() ? minMarker : maxMarker;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a marker value.
|
|
||||||
*
|
|
||||||
* @param value the value
|
|
||||||
*/
|
|
||||||
String formatMarker(int value) {
|
String formatMarker(int value) {
|
||||||
return numberFormat.format(value);
|
return numberFormat.format(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ public class ChartView extends View {
|
|||||||
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
|
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
|
||||||
R.string.description_altitude_metric,
|
R.string.description_altitude_metric,
|
||||||
R.string.description_altitude_imperial,
|
R.string.description_altitude_imperial,
|
||||||
|
R.string.description_altitude_imperial,
|
||||||
R.color.chart_altitude_fill,
|
R.color.chart_altitude_fill,
|
||||||
R.color.chart_altitude_border,
|
R.color.chart_altitude_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -224,6 +225,7 @@ public class ChartView extends View {
|
|||||||
new int[]{1, 5, 10, 20, 50, 100},
|
new int[]{1, 5, 10, 20, 50, 100},
|
||||||
R.string.description_speed_metric,
|
R.string.description_speed_metric,
|
||||||
R.string.description_speed_imperial,
|
R.string.description_speed_imperial,
|
||||||
|
R.string.description_speed_nautical,
|
||||||
R.color.chart_speed_fill,
|
R.color.chart_speed_fill,
|
||||||
R.color.chart_speed_border,
|
R.color.chart_speed_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -246,6 +248,7 @@ public class ChartView extends View {
|
|||||||
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
|
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
|
||||||
R.string.description_pace_metric,
|
R.string.description_pace_metric,
|
||||||
R.string.description_pace_imperial,
|
R.string.description_pace_imperial,
|
||||||
|
R.string.description_pace_nautical,
|
||||||
R.color.chart_pace_fill,
|
R.color.chart_pace_fill,
|
||||||
R.color.chart_pace_border,
|
R.color.chart_pace_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -268,6 +271,7 @@ public class ChartView extends View {
|
|||||||
new int[]{25, 50},
|
new int[]{25, 50},
|
||||||
R.string.description_sensor_heart_rate,
|
R.string.description_sensor_heart_rate,
|
||||||
R.string.description_sensor_heart_rate,
|
R.string.description_sensor_heart_rate,
|
||||||
|
R.string.description_sensor_heart_rate,
|
||||||
R.color.chart_heart_rate_fill,
|
R.color.chart_heart_rate_fill,
|
||||||
R.color.chart_heart_rate_border,
|
R.color.chart_heart_rate_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -289,6 +293,7 @@ public class ChartView extends View {
|
|||||||
new int[]{5, 10, 25, 50},
|
new int[]{5, 10, 25, 50},
|
||||||
R.string.description_sensor_cadence,
|
R.string.description_sensor_cadence,
|
||||||
R.string.description_sensor_cadence,
|
R.string.description_sensor_cadence,
|
||||||
|
R.string.description_sensor_cadence,
|
||||||
R.color.chart_cadence_fill,
|
R.color.chart_cadence_fill,
|
||||||
R.color.chart_cadence_border,
|
R.color.chart_cadence_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -309,6 +314,7 @@ public class ChartView extends View {
|
|||||||
new int[]{5, 50, 100, 200},
|
new int[]{5, 50, 100, 200},
|
||||||
R.string.description_sensor_power,
|
R.string.description_sensor_power,
|
||||||
R.string.description_sensor_power,
|
R.string.description_sensor_power,
|
||||||
|
R.string.description_sensor_power,
|
||||||
R.color.chart_power_fill,
|
R.color.chart_power_fill,
|
||||||
R.color.chart_power_border,
|
R.color.chart_power_border,
|
||||||
fontSizeSmall,
|
fontSizeSmall,
|
||||||
@@ -732,6 +738,8 @@ public class ChartView extends View {
|
|||||||
return context.getString(R.string.unit_kilometer);
|
return context.getString(R.string.unit_kilometer);
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return context.getString(R.string.unit_mile);
|
return context.getString(R.string.unit_mile);
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
return context.getString(R.string.unit_nautical_mile);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
private static final String TRACKID_DELIMITER = "_";
|
private static final String TRACKID_DELIMITER = "_";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*/*", URI_GPX);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getPreferenceId() + "/*/*", URI_GPX);
|
||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.CSV.getName() + "/*/*", URI_CSV);
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.CSV.getPreferenceId() + "/*/*", URI_CSV);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +80,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
/**
|
/**
|
||||||
* @return An URI for one file containing all tracks.
|
* @return An URI for one file containing all tracks.
|
||||||
*/
|
*/
|
||||||
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String filename, @NonNull TrackFileFormat trackFileFormat) {
|
||||||
if (trackIds.isEmpty()) {
|
if (trackIds.isEmpty()) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
}
|
}
|
||||||
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||||
|
|
||||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
|
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||||
String mime = getTypeMime(uri);
|
String mime = getTypeMime(uri);
|
||||||
|
|
||||||
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
|
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ public class TrackDataHub {
|
|||||||
// Also include the last point if the selected track is not recording.
|
// Also include the last point if the selected track is not recording.
|
||||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||||
for (Listener trackDataListener : listeners) {
|
for (Listener trackDataListener : listeners) {
|
||||||
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics(), currentUpdater.getSmoothedSpeed(), currentUpdater.getSmoothedAltitude());
|
trackDataListener.onSampledInTrackPoint(trackPoint, currentUpdater.getTrackStatistics(), currentUpdater.getSmoothedSpeed());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (Listener trackDataListener : listeners) {
|
for (Listener trackDataListener : listeners) {
|
||||||
@@ -432,7 +432,7 @@ public class TrackDataHub {
|
|||||||
*
|
*
|
||||||
* @param trackPoint the trackPoint
|
* @param trackPoint the trackPoint
|
||||||
*/
|
*/
|
||||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, @Nullable Altitude smoothedAltitude_m) {
|
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ public class Distance {
|
|||||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Distance ofNauticalMile(double distance_mile) {
|
||||||
|
return of(distance_mile * UnitConversions.NAUTICAL_MILE_TO_M);
|
||||||
|
}
|
||||||
|
|
||||||
public static Distance ofKilometer(double distance_km) {
|
public static Distance ofKilometer(double distance_km) {
|
||||||
return of(distance_km * UnitConversions.KM_TO_M);
|
return of(distance_km * UnitConversions.KM_TO_M);
|
||||||
}
|
}
|
||||||
@@ -51,6 +55,8 @@ public class Distance {
|
|||||||
return Distance.ofKilometer(1);
|
return Distance.ofKilometer(1);
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return Distance.ofMile(1);
|
return Distance.ofMile(1);
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
return Distance.ofNauticalMile(1);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
@@ -114,12 +120,18 @@ public class Distance {
|
|||||||
return toKM() * UnitConversions.KM_TO_MI;
|
return toKM() * UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double toNauticalMiles() {
|
||||||
|
return toKM() * UnitConversions.KM_TO_NAUTICAL_MILE;
|
||||||
|
}
|
||||||
|
|
||||||
public double toKM_Miles(UnitSystem unitSystem) {
|
public double toKM_Miles(UnitSystem unitSystem) {
|
||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC:
|
||||||
return toKM();
|
return toKM();
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return toMI();
|
return toMI();
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
return toNauticalMiles();
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
@@ -129,6 +141,7 @@ public class Distance {
|
|||||||
switch (unitSystem) {
|
switch (unitSystem) {
|
||||||
case METRIC:
|
case METRIC:
|
||||||
return toM();
|
return toM();
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return toFT();
|
return toFT();
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ public class DistanceFormatter {
|
|||||||
case METRIC:
|
case METRIC:
|
||||||
return new Pair<>(null, resources.getString(R.string.unit_meter));
|
return new Pair<>(null, resources.getString(R.string.unit_meter));
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
return new Pair<>(null, resources.getString(R.string.unit_feet));
|
return new Pair<>(null, resources.getString(R.string.unit_feet));
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
@@ -65,6 +66,12 @@ public class DistanceFormatter {
|
|||||||
} else {
|
} else {
|
||||||
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||||
}
|
}
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
if (distance.greaterThan(Distance.ofNauticalMile(0.5))) {
|
||||||
|
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
||||||
|
} else {
|
||||||
|
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ public class Speed {
|
|||||||
return toKMH() * UnitConversions.KM_TO_MI;
|
return toKMH() * UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double toKnots() {
|
||||||
|
return toKMH() * UnitConversions.KM_TO_NAUTICAL_MILE;
|
||||||
|
}
|
||||||
|
|
||||||
public Duration toPace(UnitSystem unitSystem) {
|
public Duration toPace(UnitSystem unitSystem) {
|
||||||
if (isZero()) {
|
if (isZero()) {
|
||||||
return Duration.ofSeconds(0);
|
return Duration.ofSeconds(0);
|
||||||
@@ -106,6 +110,9 @@ public class Speed {
|
|||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
distance *= UnitConversions.M_TO_MI;
|
distance *= UnitConversions.M_TO_MI;
|
||||||
break;
|
break;
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
distance *= UnitConversions.M_TO_NAUTICAL_MILE;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
@@ -119,6 +126,8 @@ public class Speed {
|
|||||||
return toKMH();
|
return toKMH();
|
||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
return toMPH();
|
return toMPH();
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
return toKnots();
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public class SpeedFormatter {
|
|||||||
case IMPERIAL:
|
case IMPERIAL:
|
||||||
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||||
break;
|
break;
|
||||||
|
case NAUTICAL_IMPERIAL:
|
||||||
|
unitId = reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class TrackPoint {
|
|||||||
private Double longitude;
|
private Double longitude;
|
||||||
private Distance horizontalAccuracy;
|
private Distance horizontalAccuracy;
|
||||||
private Distance verticalAccuracy;
|
private Distance verticalAccuracy;
|
||||||
private Altitude altitude;
|
private Altitude altitude; //TODO use Altitude.WGS84
|
||||||
private Speed speed;
|
private Speed speed;
|
||||||
private Float bearing;
|
private Float bearing;
|
||||||
private Distance sensorDistance;
|
private Distance sensorDistance;
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ public class UnitConversions {
|
|||||||
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
|
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
|
||||||
// multiplication factor to convert meters to feet
|
// multiplication factor to convert meters to feet
|
||||||
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
|
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
|
||||||
|
public static final double NAUTICAL_MILE_TO_M = 1852.0;
|
||||||
|
public static final double M_TO_NAUTICAL_MILE = 1 / NAUTICAL_MILE_TO_M;
|
||||||
|
public static final double KM_TO_NAUTICAL_MILE = 1000 * M_TO_NAUTICAL_MILE;
|
||||||
// multiplication factor to convert meters per second to kilometers per hour
|
// multiplication factor to convert meters per second to kilometers per hour
|
||||||
public static final double MPS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
|
public static final double MPS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
|
||||||
// multiplication factor to convert miles to km
|
// multiplication factor to convert miles to km
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||||
service.getRecordingDataObservable()
|
service.getRecordingDataObservable()
|
||||||
.observe(StatisticsRecordingFragment.this, this::onRecordingDataChanged);
|
.observe(StatisticsRecordingFragment.this, this::onRecordingDataChanged);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package de.dennisguse.opentracks.io.file;
|
package de.dennisguse.opentracks.io.file;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
@@ -19,7 +23,7 @@ import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
|||||||
*/
|
*/
|
||||||
public enum TrackFileFormat {
|
public enum TrackFileFormat {
|
||||||
|
|
||||||
KML_WITH_TRACKDETAIL_AND_SENSORDATA {
|
KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(Context context) {
|
||||||
return new KMLTrackExporter(context, false);
|
return new KMLTrackExporter(context, false);
|
||||||
@@ -36,7 +40,7 @@ public enum TrackFileFormat {
|
|||||||
},
|
},
|
||||||
|
|
||||||
@Deprecated //TODO Check if we really need this
|
@Deprecated //TODO Check if we really need this
|
||||||
KMZ_WITH_TRACKDETAIL_AND_SENSORDATA {
|
KMZ_WITH_TRACKDETAIL_AND_SENSORDATA("KMZ_WITH_TRACKDETAIL_AND_SENSORDATA") {
|
||||||
|
|
||||||
private static final boolean exportPhotos = false;
|
private static final boolean exportPhotos = false;
|
||||||
|
|
||||||
@@ -61,7 +65,7 @@ public enum TrackFileFormat {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES {
|
KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES("KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES") {
|
||||||
|
|
||||||
private static final boolean exportPhotos = true;
|
private static final boolean exportPhotos = true;
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
GPX {
|
GPX("GPX") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(Context context) {
|
||||||
return new GPXTrackExporter(new ContentProviderUtils(context), context.getString(R.string.app_name));
|
return new GPXTrackExporter(new ContentProviderUtils(context), context.getString(R.string.app_name));
|
||||||
@@ -103,7 +107,7 @@ public enum TrackFileFormat {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
CSV {
|
CSV("CSV") {
|
||||||
@Override
|
@Override
|
||||||
public TrackExporter createTrackExporter(Context context) {
|
public TrackExporter createTrackExporter(Context context) {
|
||||||
return new CSVTrackExporter(new ContentProviderUtils(context));
|
return new CSVTrackExporter(new ContentProviderUtils(context));
|
||||||
@@ -124,6 +128,29 @@ public enum TrackFileFormat {
|
|||||||
|
|
||||||
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
|
||||||
|
|
||||||
|
private final String preferenceId;
|
||||||
|
|
||||||
|
TrackFileFormat(String preferenceId) {
|
||||||
|
this.preferenceId = preferenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> toPreferenceIdLabelMap(final Resources resources, final TrackFileFormat ... trackFileFormats) {
|
||||||
|
Map<String, String> preferenceIdLabelMap = new LinkedHashMap<>(trackFileFormats.length);
|
||||||
|
for (TrackFileFormat trackFileFormat : trackFileFormats) {
|
||||||
|
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
|
||||||
|
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
|
||||||
|
preferenceIdLabelMap.put(trackFileFormat.getPreferenceId(), String.format("%s (%s)", trackFileFormatUpperCase, resources.getString(photoMessageId)));
|
||||||
|
}
|
||||||
|
return preferenceIdLabelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TrackFileFormat valueOfPreferenceId(final String preferenceId) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(trackFileFormat -> trackFileFormat.getPreferenceId().equals(preferenceId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the mime type for each format.
|
* Returns the mime type for each format.
|
||||||
*/
|
*/
|
||||||
@@ -149,9 +176,9 @@ public enum TrackFileFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name for each format.
|
* The identifier to be stored in the preferences.
|
||||||
*/
|
*/
|
||||||
public String getName() {
|
public String getPreferenceId() {
|
||||||
return this.name().toLowerCase(Locale.US);
|
return preferenceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,10 +21,11 @@ public class TrackFilenameGenerator {
|
|||||||
public static final String UUID_KEY = "{uuid}";
|
public static final String UUID_KEY = "{uuid}";
|
||||||
public static final String TRACKNAME_KEY = "{name}";
|
public static final String TRACKNAME_KEY = "{name}";
|
||||||
public static final String CATEGORY_KEY = "{category}";
|
public static final String CATEGORY_KEY = "{category}";
|
||||||
public static final String STARTTIME_KEY = "{time}";
|
public static final String STARTTIME_TIME_KEY = "{time}";
|
||||||
|
public static final String STARTTIME_DATE_KEY = "{date}";
|
||||||
|
|
||||||
public static String getAllOptions() {
|
public static String getAllOptions() {
|
||||||
return Stream.of(UUID_KEY, TRACKNAME_KEY, CATEGORY_KEY, STARTTIME_KEY)
|
return Stream.of(UUID_KEY, TRACKNAME_KEY, CATEGORY_KEY, STARTTIME_TIME_KEY, STARTTIME_DATE_KEY)
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +41,8 @@ public class TrackFilenameGenerator {
|
|||||||
values.put(UUID_KEY, track.getUuid().toString().substring(0, 8));
|
values.put(UUID_KEY, track.getUuid().toString().substring(0, 8));
|
||||||
values.put(TRACKNAME_KEY, track.getName());
|
values.put(TRACKNAME_KEY, track.getName());
|
||||||
values.put(CATEGORY_KEY, track.getCategory());
|
values.put(CATEGORY_KEY, track.getCategory());
|
||||||
values.put(STARTTIME_KEY, track.getStartTime().toString());
|
values.put(STARTTIME_TIME_KEY, track.getStartTime().toLocalTime().toString());
|
||||||
|
values.put(STARTTIME_DATE_KEY, track.getStartTime().toLocalDate().toString());
|
||||||
|
|
||||||
return FileUtils.sanitizeFileName(format(template, values)) + "." + trackFileFormat.getExtension();
|
return FileUtils.sanitizeFileName(format(template, values)) + "." + trackFileFormat.getExtension();
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,7 @@ public class TrackFilenameGenerator {
|
|||||||
try {
|
try {
|
||||||
getExample();
|
getExample();
|
||||||
return !template.isEmpty();
|
return !template.isEmpty();
|
||||||
} catch (TemplateInvalidException e) {
|
} catch (TemplateInvalidException | NullPointerException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +96,7 @@ public class TrackFilenameGenerator {
|
|||||||
track.setUuid(UUID.fromString("fefefefefe-0000-1000-8000-00805f9b34fb"));
|
track.setUuid(UUID.fromString("fefefefefe-0000-1000-8000-00805f9b34fb"));
|
||||||
track.getTrackStatistics().setStartTime(Instant.ofEpochMilli(0));
|
track.getTrackStatistics().setStartTime(Instant.ofEpochMilli(0));
|
||||||
|
|
||||||
return format(track, TrackFileFormat.GPX);
|
return format(track, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TemplateInvalidException extends RuntimeException {
|
public static class TemplateInvalidException extends RuntimeException {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class CSVTrackExporter implements TrackExporter {
|
|||||||
new Column("time", null),
|
new Column("time", null),
|
||||||
new Column("trackpoint_type", t -> quote(t.getType().name())),
|
new Column("trackpoint_type", t -> quote(t.getType().name())),
|
||||||
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
||||||
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
|
||||||
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
|
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
|
||||||
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
|
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
|
||||||
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
|
|||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
||||||
import de.dennisguse.opentracks.databinding.ToolbarBinding;
|
|
||||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.ExportUtils;
|
import de.dennisguse.opentracks.util.ExportUtils;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
@@ -135,7 +135,6 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
|
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
|
||||||
ToolbarBinding mergedBinding = ToolbarBinding.bind(viewBinding.getRoot());
|
|
||||||
setContentView(viewBinding.getRoot());
|
setContentView(viewBinding.getRoot());
|
||||||
|
|
||||||
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||||
@@ -146,8 +145,6 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
String directoryDisplayName = FileUtils.getPath(documentFile);
|
String directoryDisplayName = FileUtils.getPath(documentFile);
|
||||||
|
|
||||||
mergedBinding.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
|
||||||
|
|
||||||
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
|
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
@@ -170,6 +167,9 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
setProgress();
|
setProgress();
|
||||||
initExport(getTotalDone());
|
initExport(getTotalDone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.export_progress_message, directoryDisplayName));
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -235,7 +235,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
* @param conflictResolution conflict resolution to be applied if needed.
|
* @param conflictResolution conflict resolution to be applied if needed.
|
||||||
*/
|
*/
|
||||||
private void export(Track track, int conflictResolution) {
|
private void export(Track track, int conflictResolution) {
|
||||||
boolean fileExists = ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles);
|
boolean fileExists = exportFileExists(track);
|
||||||
|
|
||||||
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
||||||
conflict(track);
|
conflict(track);
|
||||||
@@ -251,6 +251,11 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
export(track, autoConflict);
|
export(track, autoConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean exportFileExists(Track track) {
|
||||||
|
String filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||||
|
return directoryFiles.stream().anyMatch(filename::equals);
|
||||||
|
}
|
||||||
|
|
||||||
private void setConflictVisibility(int visibility) {
|
private void setConflictVisibility(int visibility) {
|
||||||
viewBinding.exportProgressAlertIcon.setVisibility(visibility);
|
viewBinding.exportProgressAlertIcon.setVisibility(visibility);
|
||||||
viewBinding.exportProgressAlertMsg.setVisibility(visibility);
|
viewBinding.exportProgressAlertMsg.setVisibility(visibility);
|
||||||
@@ -316,7 +321,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
public void onExportSuccess(Track.Id trackId) {
|
public void onExportSuccess(Track.Id trackId) {
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
|
||||||
if (ExportUtils.isExportFileExists(track.getUuid(), trackFileFormat.getExtension(), directoryFiles)) {
|
if (exportFileExists(track)) {
|
||||||
trackExportOverwrittenCount++;
|
trackExportOverwrittenCount++;
|
||||||
} else {
|
} else {
|
||||||
trackExportSuccessCount++;
|
trackExportSuccessCount++;
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
public static final String EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL = "accuracy_horizontal";
|
public static final String EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL = "accuracy_horizontal";
|
||||||
public static final String EXTENDED_DATA_TYPE_ACCURACY_VERTICAL = "accuracy_vertical";
|
public static final String EXTENDED_DATA_TYPE_ACCURACY_VERTICAL = "accuracy_vertical";
|
||||||
|
|
||||||
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
|
||||||
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
|
|
||||||
|
|
||||||
private static final NumberFormat SENSOR_DATA_FORMAT = NumberFormat.getInstance(Locale.US);
|
private static final NumberFormat SENSOR_DATA_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -536,7 +533,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>");
|
printWriter.println("<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>");
|
||||||
printWriter.println("<IconStyle>");
|
printWriter.println("<IconStyle>");
|
||||||
printWriter.println("<scale>1.3</scale>");
|
printWriter.println("<scale>1.3</scale>");
|
||||||
printWriter.println("<Icon><href>" + TRACK_ICON + "</href></Icon>");
|
printWriter.println("<Icon />");
|
||||||
printWriter.println("</IconStyle>");
|
printWriter.println("</IconStyle>");
|
||||||
printWriter.println("</Style>");
|
printWriter.println("</Style>");
|
||||||
}
|
}
|
||||||
@@ -546,9 +543,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
*/
|
*/
|
||||||
private void writePlacemarkerStyle() {
|
private void writePlacemarkerStyle() {
|
||||||
printWriter.println("<Style id=\"" + KMLTrackExporter.MARKER_STYLE + "\"><IconStyle>");
|
printWriter.println("<Style id=\"" + KMLTrackExporter.MARKER_STYLE + "\"><IconStyle>");
|
||||||
printWriter.println("<scale>1.3</scale>");
|
printWriter.println("<Icon />");
|
||||||
printWriter.println("<Icon><href>" + KMLTrackExporter.MARKER_ICON + "</href></Icon>");
|
|
||||||
printWriter.println("<hotSpot x=\"" + 20 + "\" y=\"" + 2 + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
|
|
||||||
printWriter.println("</IconStyle></Style>");
|
printWriter.println("</IconStyle></Style>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.view.View;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -71,8 +70,6 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
viewBinding = ImportActivityBinding.inflate(getLayoutInflater());
|
||||||
setContentView(viewBinding.getRoot());
|
setContentView(viewBinding.getRoot());
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final ClipData intentClipData = intent.getClipData();
|
final ClipData intentClipData = intent.getClipData();
|
||||||
|
|
||||||
@@ -106,9 +103,6 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
documentFiles = documentUris.stream().map(it -> DocumentFile.fromSingleUri(this, it)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
|
||||||
toolbar.setTitle(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
|
||||||
|
|
||||||
initViews();
|
initViews();
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ImportViewModel.class);
|
||||||
@@ -116,6 +110,10 @@ public class ImportActivity extends FragmentActivity {
|
|||||||
summary = data;
|
summary = data;
|
||||||
setProgress();
|
setProgress();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Works for a directory, but we might have received multiple files via SEND_MULTIPLE.
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.import_progress_message, documentFiles.get(0).getName()));
|
||||||
|
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,27 +16,25 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private final String TAG = AbstractAPIActivity.class.getSimpleName();
|
private final String TAG = AbstractAPIActivity.class.getSimpleName();
|
||||||
|
|
||||||
private final TrackRecordingServiceConnection.Callback serviceConnectedCallback = service -> {
|
private final TrackRecordingServiceConnection.Callback serviceConnectedCallback = (service, connection) -> {
|
||||||
if (!isFinishing() && !isDestroyed()) {
|
if (!isFinishing() && !isDestroyed()) {
|
||||||
execute(service);
|
execute(service);
|
||||||
}
|
}
|
||||||
if (isPostExecuteStopService()) {
|
if (isPostExecuteStopService()) {
|
||||||
AbstractAPIActivity.this.trackRecordingServiceConnection.unbindAndStop(AbstractAPIActivity.this);
|
connection.unbindAndStop(AbstractAPIActivity.this);
|
||||||
} else {
|
} else {
|
||||||
AbstractAPIActivity.this.trackRecordingServiceConnection.unbind(AbstractAPIActivity.this);
|
connection.unbind(AbstractAPIActivity.this);
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
};
|
};
|
||||||
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (PreferencesUtils.isPublicAPIenabled()) {
|
if (PreferencesUtils.isPublicAPIenabled()) {
|
||||||
Log.i(TAG, "Received and trying to execute requested action.");
|
Log.i(TAG, "Received and trying to execute requested action.");
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(serviceConnectedCallback);
|
new TrackRecordingServiceConnection(serviceConnectedCallback)
|
||||||
trackRecordingServiceConnection.startAndBind(this);
|
.startAndBind(this);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
|
||||||
Log.w(TAG, "Public API is disabled; ignoring request.");
|
Log.w(TAG, "Public API is disabled; ignoring request.");
|
||||||
@@ -44,12 +42,6 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
trackRecordingServiceConnection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void execute(TrackRecordingService service);
|
protected abstract void execute(TrackRecordingService service);
|
||||||
|
|
||||||
protected abstract boolean isPostExecuteStopService();
|
protected abstract boolean isPostExecuteStopService();
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
package de.dennisguse.opentracks.publicapi;
|
package de.dennisguse.opentracks.publicapi;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
import de.dennisguse.opentracks.services.RecordingData;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
|
import de.dennisguse.opentracks.util.ExportUtils;
|
||||||
|
|
||||||
public class StopRecording extends AbstractAPIActivity {
|
public class StopRecording extends AbstractAPIActivity {
|
||||||
|
|
||||||
protected void execute(TrackRecordingService service) {
|
protected void execute(TrackRecordingService service) {
|
||||||
|
RecordingData recordingData = service.getRecordingDataObservable().getValue();
|
||||||
|
Track.Id trackId = null;
|
||||||
|
if (recordingData != null && recordingData.getTrack() != null) {
|
||||||
|
trackId = recordingData.getTrack().getId();
|
||||||
|
}
|
||||||
|
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
|
if (trackId != null) {
|
||||||
|
ExportUtils.postWorkoutExport(this, trackId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import android.bluetooth.BluetoothGattDescriptor;
|
|||||||
import android.bluetooth.BluetoothGattService;
|
import android.bluetooth.BluetoothGattService;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -110,7 +112,12 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
SensorData<DataType> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
SensorData<DataType> sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
||||||
if (sensorData != null) {
|
if (sensorData != null) {
|
||||||
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
|
Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
observer.onChanged(sensorData);
|
observer.onChanged(sensorData);
|
||||||
|
} else {
|
||||||
|
//TODO This might lead to NPEs in case of race conditions due to shutdown.
|
||||||
|
observer.getHandler().post(() -> observer.onChanged(sensorData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -128,7 +135,11 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
Log.d(TAG, "Connecting to: " + device);
|
Log.d(TAG, "Connecting to: " + device);
|
||||||
|
|
||||||
bluetoothGatt = device.connectGatt(context, true, connectCallback);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, this.observer.getHandler());
|
||||||
|
} else {
|
||||||
|
bluetoothGatt = device.connectGatt(context, false, connectCallback);
|
||||||
|
}
|
||||||
SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
|
SensorData<?> sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress());
|
||||||
observer.onChanged(sensorData);
|
observer.onChanged(sensorData);
|
||||||
}
|
}
|
||||||
@@ -275,5 +286,8 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
void onChanged(SensorData<?> sensorData);
|
void onChanged(SensorData<?> sensorData);
|
||||||
|
|
||||||
void onDisconnecting(SensorData<?> sensorData);
|
void onDisconnecting(SensorData<?> sensorData);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Handler getHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothAdapter;
|
|||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -34,6 +35,7 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
|
|||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PermissionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
||||||
@@ -57,7 +59,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
|
|
||||||
private final BluetoothAdapter bluetoothAdapter;
|
private final BluetoothAdapter bluetoothAdapter;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
private final Handler handler;
|
||||||
private boolean started = false;
|
private boolean started = false;
|
||||||
|
|
||||||
private Distance preferenceWheelCircumference;
|
private Distance preferenceWheelCircumference;
|
||||||
@@ -111,8 +113,9 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull SensorDataSetChangeObserver observer) {
|
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorDataSetChangeObserver observer) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.handler = handler;
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||||
}
|
}
|
||||||
@@ -159,6 +162,9 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
} else {
|
} else {
|
||||||
connectionManager.disconnect();
|
connectionManager.disconnect();
|
||||||
}
|
}
|
||||||
|
if (!PermissionUtils.hasBluetoothPermissions(context)) {
|
||||||
|
Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
|
||||||
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||||
try {
|
try {
|
||||||
@@ -218,6 +224,12 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
sensorDataSet.remove(sensorData);
|
sensorDataSet.remove(sensorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Handler getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
public interface SensorDataSetChangeObserver {
|
public interface SensorDataSetChangeObserver {
|
||||||
void onChange(SensorDataSet sensorDataSet);
|
void onChange(SensorDataSet sensorDataSet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import de.dennisguse.opentracks.data.models.Track;
|
|||||||
|
|
||||||
public class RecordingStatus {
|
public class RecordingStatus {
|
||||||
private final Track.Id trackId;
|
private final Track.Id trackId;
|
||||||
private final boolean paused;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
RecordingStatus(Track.Id trackId, boolean paused) {
|
RecordingStatus(Track.Id trackId) {
|
||||||
this.trackId = trackId;
|
this.trackId = trackId;
|
||||||
this.paused = paused;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track.Id getTrackId() {
|
public Track.Id getTrackId() {
|
||||||
@@ -25,24 +23,12 @@ public class RecordingStatus {
|
|||||||
return trackId != null;
|
return trackId != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPaused() {
|
|
||||||
return paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRecordingAndNotPaused() {
|
|
||||||
return isRecording() && !isPaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
static RecordingStatus notRecording() {
|
static RecordingStatus notRecording() {
|
||||||
return new RecordingStatus(null, false);
|
return new RecordingStatus(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RecordingStatus record(@NonNull Track.Id trackId) {
|
static RecordingStatus record(@NonNull Track.Id trackId) {
|
||||||
return new RecordingStatus(trackId, false);
|
return new RecordingStatus(trackId);
|
||||||
}
|
|
||||||
|
|
||||||
RecordingStatus pause() {
|
|
||||||
return new RecordingStatus(getTrackId(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecordingStatus stop() {
|
public RecordingStatus stop() {
|
||||||
@@ -54,7 +40,6 @@ public class RecordingStatus {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "RecordingStatus{" +
|
return "RecordingStatus{" +
|
||||||
"trackId=" + trackId +
|
"trackId=" + trackId +
|
||||||
", paused=" + paused +
|
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +48,11 @@ public class RecordingStatus {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
RecordingStatus that = (RecordingStatus) o;
|
RecordingStatus that = (RecordingStatus) o;
|
||||||
return paused == that.paused && Objects.equals(trackId, that.trackId);
|
return Objects.equals(trackId, that.trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(trackId, paused);
|
return Objects.hash(trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,25 +82,23 @@ class TrackRecordingManager implements SharedPreferences.OnSharedPreferenceChang
|
|||||||
return trackId;
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Handle non-existing trackId? Start a new track or exception?
|
/**
|
||||||
void resumeExistingTrack(@NonNull Track.Id resumeTrackId, @NonNull TrackPointCreator trackPointCreator) {
|
* @return if the recording could be started.
|
||||||
|
*/
|
||||||
|
boolean resumeExistingTrack(@NonNull Track.Id resumeTrackId, @NonNull TrackPointCreator trackPointCreator) {
|
||||||
trackId = resumeTrackId;
|
trackId = resumeTrackId;
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||||
onNewTrackPoint(trackPointCreator.createSegmentStartManual());
|
onNewTrackPoint(trackPointCreator.createSegmentStartManual());
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}
|
|
||||||
|
|
||||||
void pause(TrackPointCreator trackPointCreator) {
|
return true;
|
||||||
insertTrackPoint(trackPointCreator.createSegmentEnd(), true);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void end(TrackPointCreator trackPointCreator) {
|
void end(TrackPointCreator trackPointCreator) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks.services;
|
|||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -29,7 +30,6 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Marker;
|
import de.dennisguse.opentracks.data.models.Marker;
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
@@ -39,7 +39,6 @@ import de.dennisguse.opentracks.services.announcement.VoiceAnnouncementManager;
|
|||||||
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.util.ExportUtils;
|
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
|
||||||
public class TrackRecordingService extends Service implements TrackPointCreator.Callback {
|
public class TrackRecordingService extends Service implements TrackPointCreator.Callback {
|
||||||
@@ -102,7 +101,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
handler = new Handler();
|
handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
recordingStatusObservable = new MutableLiveData<>();
|
recordingStatusObservable = new MutableLiveData<>();
|
||||||
updateRecordingStatus(STATUS_DEFAULT);
|
updateRecordingStatus(STATUS_DEFAULT);
|
||||||
@@ -178,23 +177,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
public void resumeTrack(Track.Id trackId) {
|
public void resumeTrack(Track.Id trackId) {
|
||||||
trackPointCreator.reset();
|
trackPointCreator.reset();
|
||||||
trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator);
|
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
|
||||||
|
Log.w(TAG, "Cannot resume a non-existing track.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateRecordingStatus(RecordingStatus.record(trackId));
|
updateRecordingStatus(RecordingStatus.record(trackId));
|
||||||
|
|
||||||
startRecording();
|
startRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
|
||||||
public void resumeCurrentTrack() {
|
|
||||||
if (!isRecording() || !isPaused()) {
|
|
||||||
Log.w(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeTrack(recordingStatus.getTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startRecording() {
|
private void startRecording() {
|
||||||
// Update instance variables
|
// Update instance variables
|
||||||
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||||
@@ -212,7 +204,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private void startSensors() {
|
private void startSensors() {
|
||||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||||
trackPointCreator.start(this);
|
trackPointCreator.start(this, handler);
|
||||||
showNotification(true);
|
showNotification(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,48 +214,18 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to remember the recordingTrackId before setting it to null
|
|
||||||
boolean wasPause = isPaused();
|
|
||||||
Track.Id trackId = recordingStatus.getTrackId();
|
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
updateRecordingStatus(STATUS_DEFAULT);
|
updateRecordingStatus(STATUS_DEFAULT);
|
||||||
|
|
||||||
if (!wasPause) {
|
|
||||||
trackRecordingManager.end(trackPointCreator);
|
trackRecordingManager.end(trackPointCreator);
|
||||||
}
|
endRecording();
|
||||||
|
|
||||||
endRecording(true);
|
|
||||||
|
|
||||||
ExportUtils.postWorkoutExport(this, trackId);
|
|
||||||
|
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
private void endRecording() {
|
||||||
public void pauseCurrentTrack() {
|
|
||||||
if (!isRecording() || isPaused()) {
|
|
||||||
Log.w(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set recording status
|
|
||||||
updateRecordingStatus(recordingStatus.pause());
|
|
||||||
|
|
||||||
trackRecordingManager.pause(trackPointCreator);
|
|
||||||
|
|
||||||
endRecording(false);
|
|
||||||
|
|
||||||
notificationManager.updateContent(getString(R.string.generic_paused));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void endRecording(boolean trackStopped) {
|
|
||||||
stopUpdateRecordingData();
|
stopUpdateRecordingData();
|
||||||
if (!trackStopped) {
|
|
||||||
updateRecordingDataWhileRecording();
|
|
||||||
} else {
|
|
||||||
recordingDataObservable.postValue(NOT_RECORDING);
|
recordingDataObservable.postValue(NOT_RECORDING);
|
||||||
}
|
|
||||||
|
|
||||||
voiceAnnouncementManager.stop();
|
voiceAnnouncementManager.stop();
|
||||||
|
|
||||||
@@ -291,8 +253,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
public boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
||||||
if (!isRecording() || isPaused()) {
|
if (!isRecording()) {
|
||||||
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
Log.w(TAG, "Ignore newTrackPoint. Not recording.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +287,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||||
if (!isRecording() || isPaused()) {
|
if (!isRecording()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,9 +362,4 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
public boolean isRecording() {
|
public boolean isRecording() {
|
||||||
return recordingStatus.isRecording();
|
return recordingStatus.isRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public boolean isPaused() {
|
|
||||||
return recordingStatus.isPaused();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -92,15 +92,10 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onConnected(trackRecordingService);
|
callback.onConnected(trackRecordingService, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resumes the track recording service connection.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
*/
|
|
||||||
public void startConnection(@NonNull Context context) {
|
public void startConnection(@NonNull Context context) {
|
||||||
if (trackRecordingService != null) {
|
if (trackRecordingService != null) {
|
||||||
// Service is already started and bound.
|
// Service is already started and bound.
|
||||||
@@ -115,6 +110,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
/**
|
/**
|
||||||
* Unbinds the service (but leave it running).
|
* Unbinds the service (but leave it running).
|
||||||
*/
|
*/
|
||||||
|
//TODO This is often called for one-shot operations and should be refactored as unbinding is required.
|
||||||
public void unbind(Context context) {
|
public void unbind(Context context) {
|
||||||
try {
|
try {
|
||||||
context.unbindService(this);
|
context.unbindService(this);
|
||||||
@@ -141,7 +137,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
trackRecordingService = value;
|
trackRecordingService = value;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
callback.onConnected(value);
|
callback.onConnected(value, this);
|
||||||
} else {
|
} else {
|
||||||
callback.onDisconnected();
|
callback.onDisconnected();
|
||||||
}
|
}
|
||||||
@@ -171,17 +167,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
setTrackRecordingService(null);
|
setTrackRecordingService(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resumeTrack() {
|
|
||||||
if (trackRecordingService != null) {
|
|
||||||
trackRecordingService.resumeCurrentTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void pauseTrack() {
|
|
||||||
if (trackRecordingService != null) {
|
|
||||||
trackRecordingService.pauseCurrentTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Marker.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
|
public Marker.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
|
||||||
TrackRecordingService trackRecordingService = getServiceIfBound();
|
TrackRecordingService trackRecordingService = getServiceIfBound();
|
||||||
@@ -214,7 +199,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onConnected(TrackRecordingService service);
|
void onConnected(TrackRecordingService service, TrackRecordingServiceConnection connection);
|
||||||
|
|
||||||
default void onDisconnected() {
|
default void onDisconnected() {
|
||||||
}
|
}
|
||||||
|
|||||||