forked from upstream-mirrors/OpenTracks
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 979178a6f1 | |||
| a92a1e7838 | |||
| 8cba53cfe4 | |||
| fcedb9c444 | |||
| 2ec82af8f1 | |||
| 447d1bb7a3 | |||
| 2f6765e45f | |||
| 90bf2e6ab0 | |||
| d12423522e | |||
| 24f052491b | |||
| 6b4e7d7d9b | |||
| 00fcbb070a | |||
| 708cbf0b2a | |||
| 60e2fe57f9 | |||
| 14cebf3fec | |||
| b651a8ca6a | |||
| d75472ef11 | |||
| 0b849c3d55 | |||
| 1c767c41a7 | |||
| dfa80d61d7 | |||
| fa558c9ab7 | |||
| a1a363698f | |||
| b79acfb942 | |||
| 24ea718ff2 | |||
| 6d187fa66d | |||
| ea27db13fa | |||
| 29d2e7c78c | |||
| 4b7badbecc | |||
| 0dcb8010e2 | |||
| 0a9d4b207c | |||
| c6cb388ce4 | |||
| e6e009ba14 | |||
| f8aa456549 | |||
| 167d90332e | |||
| 719a052f34 | |||
| 244ca26210 | |||
| 476f3a517b | |||
| 289e93eb5d | |||
| 18ff96733e | |||
| 0f2c5adf26 | |||
| 74954df600 | |||
| 1a14fed287 | |||
| 744853ee91 | |||
| 597e1b2c7f | |||
| 8156993921 | |||
| 9890dc28bd | |||
| b65d83010d | |||
| 1a5d06f9c6 | |||
| 723c21d7ec | |||
| 8fe0c3be7d | |||
| e64792bf3f | |||
| 21bafff761 | |||
| c33c2de51d | |||
| a6b11c1c37 | |||
| 315788308b | |||
| 547c1c5c65 | |||
| 69efd6fb82 | |||
| 3a0524f96d | |||
| 05456b44ef | |||
| b449dad619 | |||
| ccefe36489 | |||
| 90e9ab6d2a | |||
| d1061fa6cd | |||
| c0c813001f | |||
| 51498bbcf9 | |||
| 4ef1fcefbc | |||
| 9a572130a7 | |||
| e931f5ab7c |
+9
-9
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:9.0.0'
|
classpath 'com.android.tools.build:gradle:9.0.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ def getVersionName = { ->
|
|||||||
return 'git describe --tags'.execute().text.trim()
|
return 'git describe --tags'.execute().text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
def getVersionCode = { ->
|
def getVersionCodeNightly = { ->
|
||||||
return Integer.valueOf('git rev-list HEAD --count'.execute().text.trim())
|
return Integer.valueOf('git rev-list HEAD --count'.execute().text.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'de.dennisguse.opentracks'
|
applicationId 'de.dennisguse.opentracks'
|
||||||
versionCode 6539
|
versionCode 6603
|
||||||
versionName "v4.25.0"
|
versionName "v4.26.0"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -108,12 +108,12 @@ android {
|
|||||||
dimension 'version'
|
dimension 'version'
|
||||||
applicationId 'de.dennisguse.opentracks.nightly'
|
applicationId 'de.dennisguse.opentracks.nightly'
|
||||||
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.nightly'
|
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.nightly'
|
||||||
signingConfig = signingConfigs.nightly
|
|
||||||
|
|
||||||
versionCode getVersionCode()
|
versionCode getVersionCodeNightly()
|
||||||
versionName getVersionName()
|
versionName getVersionName()
|
||||||
base.archivesName = "de.dennisguse.opentracks.nightly_${getVersionCode()}"
|
base.archivesName = "de.dennisguse.opentracks.nightly_${getVersionCodeNightly()}"
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.nightly
|
||||||
}
|
}
|
||||||
irreproducible {
|
irreproducible {
|
||||||
// Non-reproducible: https://f-droid.org/de/packages/de.dennisguse.opentracks/
|
// Non-reproducible: https://f-droid.org/de/packages/de.dennisguse.opentracks/
|
||||||
@@ -130,7 +130,7 @@ android {
|
|||||||
applicationId 'de.dennisguse.opentracks.playstore'
|
applicationId 'de.dennisguse.opentracks.playstore'
|
||||||
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.playstore'
|
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.playstore'
|
||||||
|
|
||||||
base.archivesName = "de.dennisguse.opentracks.playstore_${getVersionName()}_${getVersionCode()}"
|
base.archivesName = "de.dennisguse.opentracks.playstore_${getVersionName()}_${android.defaultConfig.versionCode}"
|
||||||
|
|
||||||
signingConfig = signingConfigs.release
|
signingConfig = signingConfigs.release
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ dependencies {
|
|||||||
implementation 'androidx.core:core-splashscreen:1.2.0'
|
implementation 'androidx.core:core-splashscreen:1.2.0'
|
||||||
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
||||||
implementation 'androidx.core:core-location-altitude:1.0.0-beta01'
|
implementation 'androidx.core:core-location-altitude:1.0.0-beta01'
|
||||||
implementation 'androidx.work:work-runtime:2.11.0'
|
implementation 'androidx.work:work-runtime:2.11.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.7.0'
|
androidTestImplementation 'androidx.test:core:1.7.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v4.25.1: OpenTracks
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Since v4.24.0, the API for transferring data to OSM Dashboard was broken.
|
||||||
|
Sadly, the changes in OpenTracks could not be reverted.
|
||||||
|
Thus, the protocol version was bumped to 3.
|
||||||
|
This requires updating OSM Dashboard to a version AFTER v6.5.7.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
v4.25.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Restored compatibility with OSMDashboard (Dashboard API protocol version 2)
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Update UI immediately after resuming a track
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v4.26.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- GPS is optional: configurable in the settings
|
||||||
|
- BLE sensor address/name is only shown after successful connection
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Altitude is now shown again in EGM2008 (introduced in v4.25.0)
|
||||||
|
- Altitude and speed were shown as 0.0 briefly when data was stored
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- Refactored sensor subsystem
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
v4.24.0: OpenTracks
|
||||||
|
|
||||||
|
மாற்றங்கள்:
|
||||||
|
- PublicAPI: தரவு இருந்தால் ஸ்டார்ட் டிராக் மட்டுமே புலத்தை அமைக்கும்
|
||||||
|
- மார்க்கர் புகைப்படங்களை மீண்டும் பகிரலாம்
|
||||||
|
- புகைப்படங்கள் இல்லாமல் KMZ அகற்றப்பட்டது
|
||||||
|
- இதயத் துடிப்பு 0.0 தவறானதாகக் கருதப்படுகிறது (சென்சார் ஸ்டாடிஸ்டிக்ஸ் தவிர)
|
||||||
|
- சிறப்பு ஒருங்கிணைப்புகளுக்கான இறக்குமதி தீர்வு நீக்கப்பட்டது (100/200)
|
||||||
|
(சிறப்பு டிராக் பாயிண்ட்களை ஏற்றுமதி/இறக்குமதி செய்ய MyTracks பயன்படுத்தியது)
|
||||||
|
|
||||||
|
டெவலப்பர்:
|
||||||
|
- ட்ராக் மற்றும் மார்க்கர் பதிவுகள்
|
||||||
|
- திரட்டப்பட்ட புள்ளிவிவரங்கள் தரவுத்தளத்தில் கணக்கிடப்படுகின்றன
|
||||||
|
- TrackStatisticsUpdater மற்றும் SegmentStatisticUpdater எனப் பிரிக்கவும்
|
||||||
|
- அறிமுகப்படுத்தப்பட்ட இட்டரேட்டர் வகுப்புகள் (கர்சர் அல்லது பட்டியலைக் கடந்து செல்வதற்குப் பதிலாக)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
v4.24.1: OpenTracks
|
||||||
|
|
||||||
|
பிழைத்திருத்தம்:
|
||||||
|
- OSM டாஷ்போர்டு v6.4.1 மற்றும் அதற்கு முந்தையவற்றுடன் இணக்கத்தன்மையை மீட்டெடுக்கவும் (v4.24.0 இல் அறிமுகப்படுத்தப்பட்டது)
|
||||||
|
- எண்ணின்படி இயல்புநிலை ட்ராக் பெயரைப் பயன்படுத்தும்போது செயலிழக்கிறது (v4.24.0 இல் அறிமுகப்படுத்தப்பட்டது)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
v4.24.2: OpenTracks
|
||||||
|
|
||||||
|
பிழைத்திருத்தம்:
|
||||||
|
- அனைத்தையும் தேர்ந்தெடுக்கும்போது செயலிழக்க (v4.24.0 இல் அறிமுகப்படுத்தப்பட்டது)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
v4.25.0: OpenTracks
|
||||||
|
|
||||||
|
மாற்றங்கள்:
|
||||||
|
- பதிவுப் பொத்தான் மையமாக உள்ளது
|
||||||
|
- BLE தெர்மோமீட்டருக்கான ஆதரவு (சுற்றுச்சூழல் உணர்தல்)
|
||||||
|
|
||||||
|
பிழைத்திருத்தம்:
|
||||||
|
- இறக்குமதியில்: கோப்பு பெயரிலிருந்து வகையைப் பெறுங்கள் (URI அல்ல)
|
||||||
|
|
||||||
|
டெவலப்பர்:
|
||||||
|
- TrackPoint ஒரு பதிவு
|
||||||
|
- Android Gradle செருகுநிரல் 9.0 க்கு புதுப்பிக்கவும்
|
||||||
@@ -17,6 +17,6 @@
|
|||||||
* 深色和浅色主题,跟随系统设置。
|
* 深色和浅色主题,跟随系统设置。
|
||||||
* 无广告。
|
* 无广告。
|
||||||
|
|
||||||
自由软件/开放源代码
|
自由软件/开放源代码
|
||||||
意味着您可以使用、研究、修改和分享源代码。
|
意味着您可以使用、研究、修改和分享源代码。
|
||||||
采用 Apache 2.0 许可
|
采用 Apache 2.0 许可
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ public class EspressoDeleteTrackTest {
|
|||||||
@Test
|
@Test
|
||||||
public void espressoDeleteTrackTest() {
|
public void espressoDeleteTrackTest() {
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
onView(withId(R.id.track_list_fab_action)).perform(click());
|
onView(withId(R.id.track_list_fab_action))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
onView(withId(R.id.track_recording_fab_action))
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
@@ -46,10 +47,12 @@ public class EspressoDeleteTrackTest {
|
|||||||
.perform(longClick());
|
.perform(longClick());
|
||||||
|
|
||||||
// TrackStoppedActivity
|
// TrackStoppedActivity
|
||||||
onView(withId(R.id.finish_button)).perform(click());
|
onView(withId(R.id.finish_button))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
// select track
|
// select track
|
||||||
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0))).perform(longClick());
|
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0)))
|
||||||
|
.perform(longClick());
|
||||||
|
|
||||||
// open menu and delete selected track
|
// open menu and delete selected track
|
||||||
onView(
|
onView(
|
||||||
@@ -62,9 +65,11 @@ public class EspressoDeleteTrackTest {
|
|||||||
isDisplayed()))
|
isDisplayed()))
|
||||||
.perform(click());
|
.perform(click());
|
||||||
|
|
||||||
onView(withText("Delete")).perform(click());
|
onView(withText("Delete"))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
onView(withText("OK")).perform(click());
|
onView(withText("OK"))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
// tracklist is empty now
|
// tracklist is empty now
|
||||||
onView(allOf(withText("Start recording your next adventure here"), isDisplayed()));
|
onView(allOf(withText("Start recording your next adventure here"), isDisplayed()));
|
||||||
|
|||||||
@@ -15,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 de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||||
|
|
||||||
import androidx.test.espresso.ViewInteraction;
|
|
||||||
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;
|
||||||
@@ -45,41 +44,43 @@ public class EspressoEditTrackRecordingTest {
|
|||||||
public void espressoEditTrackRecordingTest() {
|
public void espressoEditTrackRecordingTest() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction fabRecordButton = onView(withId(R.id.track_list_fab_action));
|
onView(withId(R.id.track_list_fab_action))
|
||||||
fabRecordButton.perform(click());
|
.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
|
||||||
|
|
||||||
// wait; stay recording
|
// wait; stay recording
|
||||||
trackControllerStopButton.perform(waitFor(4000));
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
|
.perform(waitFor(4000));
|
||||||
|
|
||||||
// open menu
|
// open menu
|
||||||
openContextualActionModeOverflowMenu();
|
openContextualActionModeOverflowMenu();
|
||||||
|
|
||||||
// Click the item.
|
// Click the item.
|
||||||
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(withId(R.id.track_edit_name));
|
onView(withId(R.id.track_edit_name))
|
||||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
.perform(scrollTo(), replaceText("New Name"));
|
||||||
|
|
||||||
ViewInteraction textInputEditText2 = onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()));
|
onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()))
|
||||||
textInputEditText2.perform(closeSoftKeyboard());
|
.perform(closeSoftKeyboard());
|
||||||
|
|
||||||
// save edition
|
// save edition
|
||||||
ViewInteraction appCompatButton = onView(withId(R.id.track_edit_save));
|
onView(withId(R.id.track_edit_save))
|
||||||
appCompatButton.perform(click());
|
.perform(click());
|
||||||
|
|
||||||
// stop;
|
// stop;
|
||||||
trackControllerStopButton.perform(longClick());
|
onView(withId(R.id.track_recording_fab_action))
|
||||||
|
.perform(longClick());
|
||||||
|
|
||||||
// it's on track stopped activity and there are two buttons
|
// it's on track stopped activity and there are two buttons
|
||||||
ViewInteraction resumeButton = onView(withId(R.id.resume_button));
|
onView(withId(R.id.resume_button))
|
||||||
resumeButton.check(matches(isDisplayed()));
|
.check(matches(isDisplayed()));
|
||||||
ViewInteraction finishButton = onView(withId(R.id.finish_button));
|
onView(withId(R.id.finish_button))
|
||||||
finishButton.check(matches(isDisplayed()));
|
.check(matches(isDisplayed()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public class EspressoUITest {
|
|||||||
public void record_stop_resume_stop_finish() {
|
public void record_stop_resume_stop_finish() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
onView(withId(R.id.track_list_fab_action))
|
||||||
trackControllerRecordButton.perform(click());
|
.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity: wait to record some time and then stop
|
// TrackRecordingActivity: wait to record some time and then stop
|
||||||
@@ -66,13 +66,12 @@ 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.track_list_fab_action));
|
onView(withId(R.id.track_list_fab_action))
|
||||||
trackControllerRecordButton.perform(click());
|
.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.track_recording_fab_action));
|
|
||||||
|
|
||||||
tabLayout.perform(selectTabAtIndex(1));
|
tabLayout.perform(selectTabAtIndex(1));
|
||||||
tabLayout.perform(waitFor(1000));
|
tabLayout.perform(waitFor(1000));
|
||||||
@@ -87,7 +86,8 @@ public class EspressoUITest {
|
|||||||
tabLayout.perform(waitFor(1000));
|
tabLayout.perform(waitFor(1000));
|
||||||
|
|
||||||
// stop
|
// stop
|
||||||
trackControllerStopButton.perform(longClick());
|
onView(withId(R.id.track_recording_fab_action)).
|
||||||
|
perform(longClick());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-24
@@ -66,7 +66,8 @@ import de.dennisguse.opentracks.data.models.TrackBuilder;
|
|||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||||
@@ -74,10 +75,9 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpee
|
|||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||||
@@ -94,17 +94,19 @@ public class ExportImportTest {
|
|||||||
@Rule
|
@Rule
|
||||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||||
|
|
||||||
//For csv_export_only() as we the timezone is hardcoded in the expectation.
|
//For csv_export_only() as the timezone is hardcoded in the expectation.
|
||||||
@Rule
|
@Rule
|
||||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||||
|
|
||||||
|
private static final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void preSetUp() {
|
public static void preSetUp() {
|
||||||
// Prepare looper for Android's message queue
|
// Prepare looper for Android's message queue
|
||||||
if (Looper.myLooper() == null) Looper.prepare();
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
}
|
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
PreferencesUtils.resetPreferences(context, true);
|
||||||
|
}
|
||||||
|
|
||||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
@@ -197,7 +199,7 @@ public class ExportImportTest {
|
|||||||
));
|
));
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
trackPointCreator.getSensorManager().clear();
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||||
@@ -212,7 +214,7 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
|
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
|
||||||
|
|
||||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
trackPointCreator.getSensorManager().clear();
|
||||||
trackPointCreator.setClock("2020-02-02T02:04:00Z");
|
trackPointCreator.setClock("2020-02-02T02:04:00Z");
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
@@ -660,17 +662,17 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
private static void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
||||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||||
|
|
||||||
AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", "");
|
AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", "");
|
||||||
cyclingPower.add(new Raw<>(trackPointCreator.createNow(), new BluetoothHandlerCyclingPower.Data(Power.of(power), null)));
|
cyclingPower.add(trackPointCreator.getNow(), new CyclingPowerBluetooth.Data(Power.of(power), null));
|
||||||
sensorDataAggregator.add(cyclingPower);
|
sensorManager.setAggregator(cyclingPower);
|
||||||
|
|
||||||
|
|
||||||
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||||
avgHeartRate.add(new Raw<>(trackPointCreator.createNow(), HeartRate.of(heartRate)));
|
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(heartRate));
|
||||||
sensorDataAggregator.add(avgHeartRate);
|
sensorManager.setAggregator(avgHeartRate);
|
||||||
|
|
||||||
AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") {
|
AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") {
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -684,7 +686,7 @@ public class ExportImportTest {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sensorDataAggregator.add(cyclingCadence);
|
sensorManager.setAggregator(cyclingCadence);
|
||||||
|
|
||||||
if (distance != null && speed != null) {
|
if (distance != null && speed != null) {
|
||||||
AggregatorCyclingDistanceSpeed aggregatorCyclingDistanceSpeed = new AggregatorCyclingDistanceSpeed("", "") {
|
AggregatorCyclingDistanceSpeed aggregatorCyclingDistanceSpeed = new AggregatorCyclingDistanceSpeed("", "") {
|
||||||
@@ -699,14 +701,14 @@ public class ExportImportTest {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sensorDataAggregator.add(aggregatorCyclingDistanceSpeed);
|
sensorManager.setAggregator(aggregatorCyclingDistanceSpeed);
|
||||||
} else {
|
} else {
|
||||||
sensorDataAggregator.add(new AggregatorCyclingDistanceSpeed("", ""));
|
sensorManager.setAggregator(new AggregatorCyclingDistanceSpeed("", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||||
|
|
||||||
sensorDataAggregator.add(new AggregatorTemperature("", "'") {
|
sensorManager.setAggregator(new AggregatorTemperature("", "'") {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Temperature getAggregatedValue(Instant now) {
|
public Temperature getAggregatedValue(Instant now) {
|
||||||
@@ -722,11 +724,11 @@ public class ExportImportTest {
|
|||||||
trackPointCreator.onChange();
|
trackPointCreator.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
private static void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
||||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||||
|
|
||||||
if (altitudeGain == null) {
|
if (altitudeGain == null) {
|
||||||
sensorDataAggregator.add(new AggregatorBarometer("test", null));
|
sensorManager.getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("test", null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,10 +744,10 @@ public class ExportImportTest {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sensorDataAggregator.add(aggregatorBarometer);
|
sensorManager.getAltitudeChangeHandler().setAggregator(aggregatorBarometer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
|
public static void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
|
||||||
Location location = new Location("mock");
|
Location location = new Location("mock");
|
||||||
location.setLatitude(latitude);
|
location.setLatitude(latitude);
|
||||||
location.setLongitude(longitude);
|
location.setLongitude(longitude);
|
||||||
@@ -757,6 +759,6 @@ public class ExportImportTest {
|
|||||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||||
|
|
||||||
trackPointCreator.setClock(time);
|
trackPointCreator.setClock(time);
|
||||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ public class TrackPointAssert {
|
|||||||
|
|
||||||
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
|
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
|
||||||
if (expected.position().hasAltitude()) {
|
if (expected.position().hasAltitude()) {
|
||||||
|
Assert.assertEquals("altitude", expected.position().altitude().getClass(), actual.position().altitude().getClass());
|
||||||
Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
|
Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package de.dennisguse.opentracks.publicapi;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class DataProviderTest {
|
||||||
|
|
||||||
|
private final ContentResolver resolver = ApplicationProvider.getApplicationContext().getContentResolver();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion2_tracks() {
|
||||||
|
// given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||||
|
String[] expectedTracksColumns = new String[]{
|
||||||
|
"_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"category",
|
||||||
|
"activity_type_localized",
|
||||||
|
"starttime",
|
||||||
|
"stoptime",
|
||||||
|
"totaltime",
|
||||||
|
"movingtime",
|
||||||
|
"totaldistance",
|
||||||
|
"maxspeed",
|
||||||
|
"avgspeed",
|
||||||
|
"avgmovingspeed",
|
||||||
|
"minelevation",
|
||||||
|
"maxelevation",
|
||||||
|
"elevationgain",
|
||||||
|
"elevationloss"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedTracksColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
// then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion2_trackpoints() {
|
||||||
|
// given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||||
|
String[] expectedTrackpointsColumns = {
|
||||||
|
"_id",
|
||||||
|
"trackid",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"time",
|
||||||
|
"type",
|
||||||
|
"speed"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedTrackpointsColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
// then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion2_markers() {
|
||||||
|
// given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||||
|
String[] expectedMarkersColumns = {
|
||||||
|
"_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"category",
|
||||||
|
"trackid",
|
||||||
|
"longitude",
|
||||||
|
"latitude",
|
||||||
|
"photoUrl",
|
||||||
|
"icon"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedMarkersColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
// then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion3_tracks() {
|
||||||
|
// given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||||
|
String[] expectedTracksColumns = {
|
||||||
|
"_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"activity_type",
|
||||||
|
"activity_type_localized",
|
||||||
|
"time_start",
|
||||||
|
"time_stop",
|
||||||
|
"duration_total",
|
||||||
|
"duration_moving",
|
||||||
|
"distance",
|
||||||
|
"speed_max",
|
||||||
|
"altitude_min",
|
||||||
|
"altitude_max",
|
||||||
|
"altitude_gain",
|
||||||
|
"altitude_loss"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedTracksColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
// then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.track", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedTracksColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion3_trackpoints() {
|
||||||
|
//given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||||
|
String[] expectedTrackpointsColumns = {
|
||||||
|
"_id",
|
||||||
|
"trackid",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"time",
|
||||||
|
"type",
|
||||||
|
"speed"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedTrackpointsColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
//then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedTrackpointsColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsVersion3_markers() {
|
||||||
|
// given
|
||||||
|
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||||
|
String[] expectedMarkersColumns = {
|
||||||
|
"_id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"marker_type_localized",
|
||||||
|
"trackid",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"photoUrl"
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
try (Cursor cursor = resolver.query(
|
||||||
|
uri,
|
||||||
|
expectedMarkersColumns,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)) {
|
||||||
|
// then
|
||||||
|
assertEquals("vnd.android.cursor.dir/vnd.de.dennisguse.waypoint", resolver.getType(uri));
|
||||||
|
|
||||||
|
assertNotNull(cursor);
|
||||||
|
assertArrayEquals(expectedMarkersColumns, cursor.getColumnNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerBarometricPressureTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseEnvironmentalSensing_Pa() {
|
public void parseEnvironmentalSensing_Pa() {
|
||||||
// given
|
// given
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BarometerBluetooth.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00});
|
characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AtmosphericPressure pressure = BluetoothHandlerBarometricPressure.parseEnvironmentalSensing(characteristic);
|
AtmosphericPressure pressure = BarometerBluetooth.parseEnvironmentalSensing(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(AtmosphericPressure.ofHPA(1001.65f), pressure);
|
assertEquals(AtmosphericPressure.ofHPA(1001.65f), pressure);
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerHeartRateTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseHeartRate_uint8() {
|
public void parseHeartRate_uint8() {
|
||||||
// given
|
// given
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(HeartRate.of(60), heartRate);
|
assertEquals(HeartRate.of(60), heartRate);
|
||||||
@@ -26,11 +26,11 @@ public class BluetoothHandlerHeartRateTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseHeartRate_uint16() {
|
public void parseHeartRate_uint16() {
|
||||||
// given
|
// given
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(HeartRate.of(257), heartRate);
|
assertEquals(HeartRate.of(257), heartRate);
|
||||||
+8
-8
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@@ -8,14 +8,14 @@ import android.util.Pair;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class BluetoothHandlerCyclingDistanceSpeedTest {
|
public class CyclingDistanceSpeedBluetoothTest {
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankOnly() {
|
public void parseCyclingSpeedCadence_crankOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNull(sensor.first);
|
assertNull(sensor.first);
|
||||||
@@ -24,11 +24,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(65535 + 16777216, sensor.first.wheelRevolutionsCount());
|
assertEquals(65535 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||||
@@ -37,11 +37,11 @@ public class BluetoothHandlerCyclingDistanceSpeedTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankWheel() {
|
public void parseCyclingSpeedCadence_crankWheel() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(200 + 16777216, sensor.first.wheelRevolutionsCount());
|
assertEquals(200 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||||
+6
-6
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -6,15 +6,15 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class BluetoothHandlerCyclingPowerTest {
|
public class CyclingPowerBluetoothTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingPower_power() {
|
public void parseCyclingPower_power() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(40, powerCadence.power().getW(), 0.01);
|
assertEquals(40, powerCadence.power().getW(), 0.01);
|
||||||
@@ -22,11 +22,11 @@ public class BluetoothHandlerCyclingPowerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingPower_power_with_cadence() {
|
public void parseCyclingPower_power_with_cadence() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(0, powerCadence.power().getW(), 0.01);
|
assertEquals(0, powerCadence.power().getW(), 0.01);
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -10,15 +10,15 @@ import de.dennisguse.opentracks.data.models.Cadence;
|
|||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
|
||||||
public class BluetoothHandlerRunningSpeedAndCadenceTest {
|
public class RunningSpeedAndCadenceBluetoothTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseRunningSpeedAndCadence_with_distance() {
|
public void parseRunningSpeedAndCadence_with_distance() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(RunningSpeedAndCadenceBluetooth.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||||
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
BluetoothHandlerRunningSpeedAndCadence.Data sensor = BluetoothHandlerRunningSpeedAndCadence.parseRunningSpeedAndCadence("sensorName", characteristic);
|
RunningSpeedAndCadenceBluetooth.Data sensor = RunningSpeedAndCadenceBluetooth.parseRunningSpeedAndCadence("sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(Speed.of(5), sensor.speed());
|
assertEquals(Speed.of(5), sensor.speed());
|
||||||
+3
-3
@@ -11,7 +11,7 @@ public class AggregatorBarometerTest {
|
|||||||
|
|
||||||
private static void addSensorValue(AggregatorBarometer aggregatorBarometer, float[] values) {
|
private static void addSensorValue(AggregatorBarometer aggregatorBarometer, float[] values) {
|
||||||
for (float f : values) {
|
for (float f : values) {
|
||||||
aggregatorBarometer.add(new Raw<>(Instant.MIN, AtmosphericPressure.ofHPA(f)));
|
aggregatorBarometer.add(Instant.MIN, AtmosphericPressure.ofHPA(f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ public class AggregatorBarometerTest {
|
|||||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||||
|
|
||||||
// then
|
// then
|
||||||
Assert.assertEquals(0f, subject.aggregatedValue.gain_m(), 0.01);
|
Assert.assertEquals(0f, subject.output.gain_m(), 0.01);
|
||||||
Assert.assertEquals(15f, subject.aggregatedValue.loss_m(), 0.01);
|
Assert.assertEquals(15f, subject.output.loss_m(), 0.01);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-22
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
@@ -12,10 +13,9 @@ import java.time.Instant;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
|
||||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SensorDataCyclingTest {
|
public class SensorDataCyclingTest {
|
||||||
@@ -25,8 +25,8 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 2048)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 2048));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||||
@@ -37,8 +37,8 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 6184)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 6184));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 8016)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 8016));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(33.53, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
assertEquals(33.53, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||||
@@ -49,8 +49,8 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 2048)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 2048));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
||||||
@@ -62,8 +62,8 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 1024));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(current.hasReceivedData()); //TODO Cadence should be 0?
|
assertFalse(current.hasReceivedData()); //TODO Cadence should be 0?
|
||||||
@@ -74,8 +74,8 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, UintUtils.UINT16_MAX - 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, UintUtils.UINT16_MAX - 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 0)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 0));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||||
@@ -87,13 +87,13 @@ public class SensorDataCyclingTest {
|
|||||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(UintUtils.UINT32_MAX - 1, 1024)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(UintUtils.UINT32_MAX - 1, 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(0, 2048)));
|
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(0, 2048));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// TODO See #953
|
// TODO See #953
|
||||||
// assertEquals(60, current.getValue().getRPM(), 0.01);
|
// assertEquals(60, current.getValue().getRPM(), 0.01);
|
||||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -102,8 +102,8 @@ public class SensorDataCyclingTest {
|
|||||||
current.setWheelCircumference(Distance.ofMM(2150));
|
current.setWheelCircumference(Distance.ofMM(2150));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(1, 6184)));
|
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(1, 6184));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(2, 8016)));
|
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(2, 8016));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(2.15, current.getAggregatedValue(Instant.MIN).distance().toM(), 0.01);
|
assertEquals(2.15, current.getAggregatedValue(Instant.MIN).distance().toM(), 0.01);
|
||||||
@@ -117,14 +117,13 @@ public class SensorDataCyclingTest {
|
|||||||
current.setWheelCircumference(Distance.ofMM(2000));
|
current.setWheelCircumference(Distance.ofMM(2000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(UintUtils.UINT32_MAX - 1, 1024)));
|
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(UintUtils.UINT32_MAX - 1, 1024));
|
||||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(0, 2048)));
|
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(0, 2048));
|
||||||
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// TODO See #953
|
// TODO See #953
|
||||||
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
|
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
|
||||||
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
||||||
assertEquals(new AggregatorCyclingDistanceSpeed.Data(Distance.ZERO, Distance.ZERO, Speed.ZERO), current.getAggregatedValue(Instant.MIN));
|
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+30
-17
@@ -6,7 +6,6 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -44,12 +43,12 @@ import de.dennisguse.opentracks.data.models.Statistics;
|
|||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
|
||||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
@@ -154,7 +153,8 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
|
|
||||||
//We do not want the real GPS to interfere.
|
//We do not want the real GPS to interfere.
|
||||||
LocationManagerCompat.removeUpdates((LocationManager) context.getSystemService(Context.LOCATION_SERVICE), trackPointCreator.getSensorManager().getGpsManager());
|
GpsInternal gpsInternal = (GpsInternal)trackPointCreator.getSensorManager().getGpsHandler().driver;
|
||||||
|
LocationManagerCompat.removeUpdates(gpsInternal.locationManager, gpsInternal.locationListenerCompat);
|
||||||
|
|
||||||
String gps1 = "2020-02-02T02:02:03Z";
|
String gps1 = "2020-02-02T02:02:03Z";
|
||||||
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
||||||
@@ -339,16 +339,21 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
mockAltitudeChange(trackPointCreator, 0);
|
mockAltitudeChange(trackPointCreator, 0);
|
||||||
|
|
||||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||||
sensorManager.sensorDataAggregator.add(new AggregatorHeartRate("", ""));
|
|
||||||
// when
|
// when
|
||||||
String sensor1 = "2020-02-02T02:02:03Z";
|
String sensor1 = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(sensor1);
|
trackPointCreator.setClock(sensor1);
|
||||||
|
|
||||||
sensorManager.onChanged(new Raw<>(sensor1, HeartRate.of(5))); //Should be ignored
|
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||||
|
sensorManager.setAggregator(avgHeartRate);
|
||||||
|
|
||||||
|
//Should be ignored
|
||||||
|
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(5));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
String sensor3 = "2020-02-02T02:02:13Z";
|
String sensor3 = "2020-02-02T02:02:13Z";
|
||||||
trackPointCreator.setClock(sensor3);
|
trackPointCreator.setClock(sensor3);
|
||||||
sensorManager.onChanged(new Raw<>(sensor3, HeartRate.of(7)));
|
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(7));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
String stopTime = "2020-02-02T02:02:15Z";
|
String stopTime = "2020-02-02T02:02:15Z";
|
||||||
trackPointCreator.setClock(stopTime);
|
trackPointCreator.setClock(stopTime);
|
||||||
@@ -808,19 +813,22 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
trackPointCreator.setClock(startTime);
|
trackPointCreator.setClock(startTime);
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
|
|
||||||
|
AggregatorRunning aggregatorRunning = new AggregatorRunning("", "");
|
||||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||||
sensorManager.sensorDataAggregator.add(new AggregatorRunning("", ""));
|
sensorManager.setAggregator(aggregatorRunning);
|
||||||
sensorManager.sensorDataAggregator.barometer = null;
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String sensor1 = "2020-02-02T02:02:03Z";
|
String sensor1 = "2020-02-02T02:02:03Z";
|
||||||
trackPointCreator.setClock(sensor1);
|
trackPointCreator.setClock(sensor1);
|
||||||
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(1), Distance.ZERO))); //Should be ignored
|
//Should be ignored
|
||||||
|
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(1), Distance.ZERO));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String sensor2 = "2020-02-02T02:02:04Z";
|
String sensor2 = "2020-02-02T02:02:04Z";
|
||||||
trackPointCreator.setClock(sensor2);
|
trackPointCreator.setClock(sensor2);
|
||||||
sensorManager.onChanged(new Raw<>(sensor2, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(2), Distance.of(2))));
|
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(2), Distance.of(2)));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps1 = "2020-02-02T02:02:05Z";
|
String gps1 = "2020-02-02T02:02:05Z";
|
||||||
@@ -829,12 +837,15 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
// when
|
// when
|
||||||
String sensor3 = "2020-02-02T02:02:06Z";
|
String sensor3 = "2020-02-02T02:02:06Z";
|
||||||
trackPointCreator.setClock(sensor3);
|
trackPointCreator.setClock(sensor3);
|
||||||
sensorManager.onChanged(new Raw<>(sensor3, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(3), Distance.of(12))));
|
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(3), Distance.of(12)));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String sensor4 = "2020-02-02T02:02:07Z";
|
String sensor4 = "2020-02-02T02:02:07Z";
|
||||||
trackPointCreator.setClock(sensor4);
|
trackPointCreator.setClock(sensor4);
|
||||||
sensorManager.onChanged(new Raw<>(sensor4, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(4), Distance.of(14)))); //Should be ignored
|
//Should be ignored
|
||||||
|
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(4), Distance.of(14)));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps2 = "2020-02-02T02:02:08Z";
|
String gps2 = "2020-02-02T02:02:08Z";
|
||||||
@@ -843,7 +854,9 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
// when
|
// when
|
||||||
String sensor5 = "2020-02-02T02:02:10Z";
|
String sensor5 = "2020-02-02T02:02:10Z";
|
||||||
trackPointCreator.setClock(sensor5);
|
trackPointCreator.setClock(sensor5);
|
||||||
sensorManager.onChanged(new Raw<>(sensor5, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(5), Distance.of(16)))); //Should be ignored
|
//Should be ignored
|
||||||
|
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(5), Distance.of(16)));
|
||||||
|
sensorManager.onChange();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String gps3 = "2020-02-02T02:02:12Z";
|
String gps3 = "2020-02-02T02:02:12Z";
|
||||||
@@ -962,7 +975,7 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||||
trackPointCreator.getSensorManager().sensorDataAggregator.barometer = new AggregatorBarometer("", "") {
|
trackPointCreator.getSensorManager().getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("", "") {
|
||||||
@Override
|
@Override
|
||||||
public boolean hasReceivedData() {
|
public boolean hasReceivedData() {
|
||||||
return true;
|
return true;
|
||||||
@@ -973,7 +986,7 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
public AltitudeGainLoss getAggregatedValue(Instant now) {
|
public AltitudeGainLoss getAggregatedValue(Instant now) {
|
||||||
return new AltitudeGainLoss(altitudeGain, altitudeGain);
|
return new AltitudeGainLoss(altitudeGain, altitudeGain);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
private static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
||||||
@@ -985,6 +998,6 @@ public class TrackRecordingServiceRecordingTest {
|
|||||||
location.setSpeed(speed);
|
location.setSpeed(speed);
|
||||||
|
|
||||||
trackPointCreator.setClock(time);
|
trackPointCreator.setClock(time);
|
||||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-18
@@ -52,7 +52,6 @@ import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Testing the states of TrackRecordingService.
|
* Testing the states of TrackRecordingService.
|
||||||
* As states are checked against MutableLiveData that are updated via `postValue()` (is asynchronous to be inform other Threads), Thread.sleep() is required.
|
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackRecordingServiceStateMachineTest {
|
public class TrackRecordingServiceStateMachineTest {
|
||||||
@@ -118,13 +117,12 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void gps_startStop() throws InterruptedException {
|
public void gps_startStop() {
|
||||||
// given
|
// given
|
||||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
service.tryStartSensors();
|
service.tryStartSensors();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
@@ -133,23 +131,21 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
service.stopSensors();
|
service.stopSensors();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
// assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); TODO BUG: GPS can only be stopped if a service is recording.
|
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void recording_startStopResume_no_data() throws InterruptedException {
|
public void recording_startStopResume_no_data() {
|
||||||
// given
|
// given
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
@@ -161,7 +157,6 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
@@ -172,7 +167,6 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
@@ -183,12 +177,10 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
|
||||||
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
}
|
}
|
||||||
@@ -214,7 +206,7 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void cannotEnd_without_starting() throws InterruptedException {
|
public void cannotEnd_without_starting() {
|
||||||
// given
|
// given
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
@@ -223,7 +215,6 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||||
@@ -234,33 +225,30 @@ public class TrackRecordingServiceStateMachineTest {
|
|||||||
@Ignore("TODO Bug: GPS can be stopped although the current track is recording")
|
@Ignore("TODO Bug: GPS can be stopped although the current track is recording")
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void recording_stopGPS_noop() throws InterruptedException {
|
public void recording_stopGPS_noop() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
service.stopSensors(); //TODO Should be ignored as service is recording
|
service.stopSensors(); //TODO Should be ignored as service is recording
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
assertEquals(new RecordingStatus(trackId), service.getRecordingStatusObservable().getValue());
|
||||||
Thread.sleep(1000); //TODO Figure out why we need to wait here until the update is happening
|
|
||||||
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
assertNotEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void recording_startRecording_alreadyRecording() throws InterruptedException {
|
public void recording_startRecording_alreadyRecording() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Track.Id newTrackId = service.startNewTrack();
|
Track.Id newTrackId = service.startNewTrack();
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNotNull(trackId);
|
assertNotNull(trackId);
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static de.dennisguse.opentracks.services.TrackRecordingService.RECORDING_DATA_UPDATE_INTERVAL;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.core.location.LocationManagerCompat;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
import androidx.test.rule.ServiceTestRule;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.TestUtil;
|
||||||
|
import de.dennisguse.opentracks.data.models.Altitude;
|
||||||
|
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||||
|
import de.dennisguse.opentracks.data.models.Position;
|
||||||
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.io.file.importer.ExportImportTest;
|
||||||
|
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||||
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TrackRecordingServiceUITest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||||
|
|
||||||
|
private static final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void preSetUp() {
|
||||||
|
// Prepare looper for Android's message queue
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDataForUI_gps() throws TimeoutException, InterruptedException {
|
||||||
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
|
.getService();
|
||||||
|
RecordingData recordingData;
|
||||||
|
|
||||||
|
// given
|
||||||
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
|
trackPointCreator.setClock("2020-02-02T02:02:02Z");
|
||||||
|
|
||||||
|
// when
|
||||||
|
service.startNewTrack();
|
||||||
|
//We do not want the real GPS to interfere.
|
||||||
|
GpsInternal gpsInternal = (GpsInternal)trackPointCreator.getSensorManager().getGpsHandler().driver;
|
||||||
|
LocationManagerCompat.removeUpdates(gpsInternal.locationManager, gpsInternal.locationListenerCompat);
|
||||||
|
Thread.sleep(RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(new TrackPoint(
|
||||||
|
null,
|
||||||
|
TrackPoint.Type.TRACKPOINT,
|
||||||
|
Position.of( Instant.parse("2020-02-02T02:02:02Z")),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
service.getRecordingDataObservable().getValue().latestTrackPoint());
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
// In this step, the data is saved (and Aggregator.resetAggregated called), but data should still be shown in the UI.
|
||||||
|
trackPointCreator.setClock("2020-02-02T02:02:03Z");
|
||||||
|
ExportImportTest.sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Thread.sleep(RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||||
|
recordingData = service.getRecordingDataObservable().getValue();
|
||||||
|
new TrackPointAssert().assertEquals(new TrackPoint(
|
||||||
|
null,
|
||||||
|
TrackPoint.Type.TRACKPOINT,
|
||||||
|
new Position(
|
||||||
|
Instant.parse("2020-02-02T02:02:03Z"),
|
||||||
|
3.1234567, 14.0014567, null,
|
||||||
|
Altitude.EGM2008.of(1013.05), null,
|
||||||
|
null,
|
||||||
|
Speed.of(15)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new AltitudeGainLoss(1, 1)
|
||||||
|
),
|
||||||
|
recordingData.latestTrackPoint());
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-14
@@ -14,7 +14,6 @@ import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.espresso.ViewInteraction;
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers;
|
import androidx.test.espresso.matcher.ViewMatchers;
|
||||||
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;
|
||||||
@@ -61,28 +60,34 @@ 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.bottom_app_bar)).check(matches(isDisplayed()));
|
onView(ViewMatchers.withId(R.id.bottom_app_bar))
|
||||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_stats_button))));
|
.check(matches(isDisplayed()));
|
||||||
onView(withId(R.id.aggregated_stats_button)).perform(click());
|
onView(withId(R.id.bottom_app_bar))
|
||||||
|
.check(matches(hasDescendant(withId(R.id.aggregated_stats_button))));
|
||||||
|
onView(withId(R.id.aggregated_stats_button))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
// open FilterDialogFragment through toolbar's menu item
|
// open FilterDialogFragment through toolbar's menu item
|
||||||
onView(withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
onView(withId(R.id.bottom_app_bar))
|
||||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
.check(matches(isDisplayed()));
|
||||||
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
|
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());
|
||||||
|
|
||||||
// check there's a checkbox with ACTIVITY_TYPE_LOCALIZED text
|
// check there's a checkbox with ACTIVITY_TYPE_LOCALIZED text
|
||||||
ViewInteraction checkBox = onView(
|
onView(allOf(withId(R.id.filter_dialog_check_button), withText(ACTIVITY_TYPE_LOCALIZED),
|
||||||
allOf(withId(R.id.filter_dialog_check_button), withText(ACTIVITY_TYPE_LOCALIZED),
|
|
||||||
withParent(allOf(withId(R.id.filter_items),
|
withParent(allOf(withId(R.id.filter_items),
|
||||||
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
|
withParent(IsInstanceOf.instanceOf(android.view.ViewGroup.class)))),
|
||||||
isDisplayed()));
|
isDisplayed()))
|
||||||
checkBox.perform(waitFor(2000));
|
.perform(waitFor(2000));
|
||||||
|
|
||||||
// check there's an edit text for "from date"
|
// check there's an edit text for "from date"
|
||||||
onView(withId(R.id.filter_date_edit_text_from)).check(matches(isDisplayed()));
|
onView(withId(R.id.filter_date_edit_text_from))
|
||||||
|
.check(matches(isDisplayed()));
|
||||||
|
|
||||||
// check there's an edit text for "to date"
|
// check there's an edit text for "to date"
|
||||||
onView(withId(R.id.filter_date_edit_text_to)).check(matches(isDisplayed()));
|
onView(withId(R.id.filter_date_edit_text_to))
|
||||||
|
.check(matches(isDisplayed()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import java.util.Objects;
|
|||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||||
|
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||||
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.services.MissingPermissionException;
|
import de.dennisguse.opentracks.services.MissingPermissionException;
|
||||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||||
@@ -62,7 +63,6 @@ import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragm
|
|||||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||||
|
|
||||||
@@ -367,7 +367,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.showTrackOnMap(this, false, trackIds);
|
DataProvider.showTrackOnMap(this, false, trackIds);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.data.TrackDataHub;
|
|||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
|
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
|
||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||||
|
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
@@ -45,7 +46,6 @@ import de.dennisguse.opentracks.share.ShareUtils;
|
|||||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
||||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,7 +167,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.showTrackOnMap(this, false, trackId);
|
DataProvider.showTrackOnMap(this, false, trackId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
|
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||||
|
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||||
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
@@ -49,7 +50,6 @@ import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
|||||||
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
|
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
|
||||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,7 +267,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
@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.showTrackOnMap(this, true, trackId);
|
DataProvider.showTrackOnMap(this, true, trackId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
//TODO Move to CustomContentProvider
|
||||||
// The authority (the first part of the URI) for the app's content provider.
|
// The authority (the first part of the URI) for the app's content provider.
|
||||||
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
|
||||||
|
|
||||||
|
//TODO Move to CustomContentProvider
|
||||||
// The base URI for the app's content provider.
|
// The base URI for the app's content provider.
|
||||||
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||||
|
import de.dennisguse.opentracks.publicapi.DataProvider;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +95,6 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||||
|
|
||||||
public CustomContentProvider() {
|
public CustomContentProvider() {
|
||||||
//TODO Add tests for used URIs (incl. Dashboard API)
|
|
||||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||||
@@ -108,6 +108,10 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.MARKERS_BY_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.MARKERS_BY_ID.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.MARKERS_BY_TRACKID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.MARKERS_BY_TRACKID.ordinal());
|
||||||
|
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKS_BY_IDS.getPath() + "*", UrlType.DASHBOARD_TRACKS_BY_IDS.ordinal());
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.ordinal());
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.ordinal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -170,12 +174,21 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType(@NonNull Uri url) {
|
public String getType(@NonNull Uri url) {
|
||||||
|
String contentType = getUrlType(url).contentType;
|
||||||
|
if (contentType != null) {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO replace with getUrlType(url).getContentType()
|
||||||
return switch (getUrlType(url)) {
|
return switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||||
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||||
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||||
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||||
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||||
|
case DASHBOARD_TRACKS_BY_IDS -> UrlType.DASHBOARD_TRACKS_BY_IDS.contentType;
|
||||||
|
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.contentType;
|
||||||
|
case DASHBOARD_MARKERS_BY_TRACKIDS -> UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.contentType;
|
||||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -225,6 +238,7 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
String sortOrder = null;
|
String sortOrder = null;
|
||||||
String groupBy = null;
|
String groupBy = null;
|
||||||
|
//TODO queryBuilder.setStrict(true);
|
||||||
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
||||||
switch (getUrlType(url)) {
|
switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS -> {
|
case TRACKPOINTS -> {
|
||||||
@@ -239,6 +253,11 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
}
|
}
|
||||||
|
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> {
|
||||||
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
|
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
|
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKPOINTS);
|
||||||
|
}
|
||||||
case TRACKS -> {
|
case TRACKS -> {
|
||||||
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
|
||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
|
||||||
@@ -251,6 +270,11 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
}
|
}
|
||||||
|
case DASHBOARD_TRACKS_BY_IDS -> {
|
||||||
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
|
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
|
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKS);
|
||||||
|
}
|
||||||
case TRACKS_SENSOR_STATS -> {
|
case TRACKS_SENSOR_STATS -> {
|
||||||
//TODO Replace with SQLQueryBuilder?
|
//TODO Replace with SQLQueryBuilder?
|
||||||
long trackId = ContentUris.parseId(url);
|
long trackId = ContentUris.parseId(url);
|
||||||
@@ -278,9 +302,14 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
|
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
|
||||||
}
|
}
|
||||||
case MARKERS_BY_TRACKID -> {
|
case MARKERS_BY_TRACKID -> {
|
||||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT 'unused' AS ICON)"); //TODO Workaround: Column icon was removed in v4.24.0, but is still required for OSM Dashboard v6.4.1.
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
}
|
}
|
||||||
|
case DASHBOARD_MARKERS_BY_TRACKIDS -> {
|
||||||
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
|
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
|
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_MARKERS);
|
||||||
|
}
|
||||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||||
}
|
}
|
||||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||||
@@ -372,9 +401,11 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
throw new SQLException("Failed to insert a marker " + url);
|
throw new SQLException("Failed to insert a marker " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum UrlType {
|
//TODO Add ContentType as attribute
|
||||||
|
public enum UrlType {
|
||||||
TRACKPOINTS,
|
TRACKPOINTS,
|
||||||
TRACKPOINTS_BY_ID,
|
TRACKPOINTS_BY_ID,
|
||||||
|
//TODO Check if still used
|
||||||
TRACKPOINTS_BY_TRACKID,
|
TRACKPOINTS_BY_TRACKID,
|
||||||
TRACKS,
|
TRACKS,
|
||||||
TRACKS_BY_ID,
|
TRACKS_BY_ID,
|
||||||
@@ -382,6 +413,36 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
TRACKS_AGGREGATED_STATISTICS,
|
TRACKS_AGGREGATED_STATISTICS,
|
||||||
MARKERS,
|
MARKERS,
|
||||||
MARKERS_BY_ID,
|
MARKERS_BY_ID,
|
||||||
MARKERS_BY_TRACKID
|
@Deprecated //TODO Should not be used anymore
|
||||||
|
MARKERS_BY_TRACKID,
|
||||||
|
|
||||||
|
DASHBOARD_TRACKS_BY_IDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/"), TracksColumns.CONTENT_TYPE),
|
||||||
|
DASHBOARD_TRACKPOINTS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/"), TrackPointsColumns.CONTENT_TYPE),
|
||||||
|
DASHBOARD_MARKERS_BY_TRACKIDS(Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/"), MarkerColumns.CONTENT_TYPE);
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
//TODO Add Uri as attribute:
|
||||||
|
//TODO Add ContentType as attribute:
|
||||||
|
@Deprecated()
|
||||||
|
UrlType() {
|
||||||
|
this.uri = null;
|
||||||
|
this.contentType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlType(Uri uri, String contentType) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPath() {
|
||||||
|
return uri.getPath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 41;
|
private static final int DATABASE_VERSION = 42;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@@ -85,6 +85,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 39 -> upgradeFrom38to39(db);
|
case 39 -> upgradeFrom38to39(db);
|
||||||
case 40 -> upgradeFrom39to40(db);
|
case 40 -> upgradeFrom39to40(db);
|
||||||
case 41 -> upgradeFrom40to41(db);
|
case 41 -> upgradeFrom40to41(db);
|
||||||
|
case 42 -> upgradeFrom41to42(db);
|
||||||
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +114,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 38 -> downgradeFrom39to38(db);
|
case 38 -> downgradeFrom39to38(db);
|
||||||
case 39 -> downgradeFrom40to39(db);
|
case 39 -> downgradeFrom40to39(db);
|
||||||
case 40 -> downgradeFrom41to40(db);
|
case 40 -> downgradeFrom41to40(db);
|
||||||
|
case 41 -> downgradeFrom42to41(db);
|
||||||
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,4 +766,32 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void upgradeFrom41to42(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
|
||||||
|
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, name TEXT, description TEXT, marker_type_localized TEXT, photoUrl TEXT, longitude INTEGER, latitude INTEGER, time INTEGER, altitude FLOAT, accuracy FLOAT, bearing FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||||
|
db.execSQL("INSERT INTO markers SELECT _id, trackid, name, description, category, photoUrl, longitude, latitude, time, elevation, accuracy, bearing FROM markers_old");
|
||||||
|
db.execSQL("DROP TABLE markers_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom42to41(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
|
||||||
|
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||||
|
db.execSQL("INSERT INTO markers SELECT _id, name, description, marker_type_localized, trackid, longitude, latitude, time, altitude, accuracy, bearing, photoUrl FROM markers_old");
|
||||||
|
db.execSQL("DROP TABLE markers_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,11 +89,10 @@ 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.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||||
String mime = getTypeMime(uri);
|
|
||||||
|
|
||||||
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
|
Log.d(TAG, "Created uri " + uri + " with MIME " + trackFileFormat.getMimeType());
|
||||||
|
|
||||||
return new Pair<>(uri, mime);
|
return new Pair<>(uri, trackFileFormat.getMimeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Set<Track.Id> parseURI(Uri uri) {
|
static Set<Track.Id> parseURI(Uri uri) {
|
||||||
@@ -126,6 +125,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
|
||||||
return switch (uriMatcher.match(uri)) {
|
return switch (uriMatcher.match(uri)) {
|
||||||
case URI_GPX -> TrackFileFormat.GPX;
|
case URI_GPX -> TrackFileFormat.GPX;
|
||||||
@@ -134,16 +134,10 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
|
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
|
||||||
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||||
case URI_CSV -> TrackFileFormat.CSV;
|
case URI_CSV -> TrackFileFormat.CSV;
|
||||||
default ->
|
default -> null;
|
||||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String getTypeMime(@NonNull Uri uri) {
|
|
||||||
return getTrackFileFormat(uri).getMimeType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
|
||||||
if (uriMatcher.match(uri) == -1) {
|
if (uriMatcher.match(uri) == -1) {
|
||||||
@@ -182,9 +176,9 @@ public class ShareContentProvider extends CustomContentProvider {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public String getType(@NonNull Uri uri) {
|
public String getType(@NonNull Uri uri) {
|
||||||
String mime = getTypeMime(uri);
|
TrackFileFormat trackFileFormat = getTrackFileFormat(uri);
|
||||||
if (mime != null) {
|
if (trackFileFormat != null) {
|
||||||
return mime;
|
return trackFileFormat.getMimeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getType(uri);
|
return super.getType(uri);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public record Position(
|
public record Position(
|
||||||
@Nullable Instant time,
|
@Nullable Instant time, //TODO It may be a good idea to move time out of Position
|
||||||
@Nullable Double latitude,
|
@Nullable Double latitude,
|
||||||
@Nullable Double longitude,
|
@Nullable Double longitude,
|
||||||
@Nullable Distance horizontalAccuracy,
|
@Nullable Distance horizontalAccuracy,
|
||||||
@@ -17,10 +17,6 @@ public record Position(
|
|||||||
@Nullable Float bearing,
|
@Nullable Float bearing,
|
||||||
@Nullable Speed speed
|
@Nullable Speed speed
|
||||||
) {
|
) {
|
||||||
@Deprecated
|
|
||||||
public static Position empty() {
|
|
||||||
return of(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Position of(@NonNull Instant time) {
|
public static Position of(@NonNull Instant time) {
|
||||||
return new Position(
|
return new Position(
|
||||||
|
|||||||
@@ -39,32 +39,32 @@ public interface MarkerColumns extends BaseColumns {
|
|||||||
String DEFAULT_SORT_ORDER = _ID;
|
String DEFAULT_SORT_ORDER = _ID;
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
String NAME = "name"; // marker name
|
String NAME = "name";
|
||||||
String DESCRIPTION = "description"; // marker description
|
String DESCRIPTION = "description";
|
||||||
String TYPE_LOCALIZED = "category";
|
String TYPE_LOCALIZED = "marker_type_localized";
|
||||||
String TRACKID = "trackid"; // track id
|
String TRACKID = "trackid";
|
||||||
String LONGITUDE = "longitude"; // longitude
|
String LONGITUDE = "longitude";
|
||||||
String LATITUDE = "latitude"; // latitude
|
String LATITUDE = "latitude";
|
||||||
String TIME = "time"; // time
|
String TIME = "time";
|
||||||
String ALTITUDE = "elevation"; // altitude //TODO RENAME column
|
String ALTITUDE = "altitude";
|
||||||
String HORIZONTAL_ACCURACY = "accuracy"; // accuracy
|
String HORIZONTAL_ACCURACY = "accuracy";
|
||||||
String BEARING = "bearing"; // bearing
|
String BEARING = "bearing";
|
||||||
|
|
||||||
String PHOTOURL = "photoUrl"; // url for the photo
|
String PHOTOURL = "photoUrl"; // url for the photo
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
|
+ TRACKID + " INTEGER NOT NULL, "
|
||||||
+ NAME + " TEXT, "
|
+ NAME + " TEXT, "
|
||||||
+ DESCRIPTION + " TEXT, "
|
+ DESCRIPTION + " TEXT, "
|
||||||
+ TYPE_LOCALIZED + " TEXT, "
|
+ TYPE_LOCALIZED + " TEXT, "
|
||||||
+ TRACKID + " INTEGER NOT NULL, "
|
+ PHOTOURL + " TEXT, "
|
||||||
+ LONGITUDE + " INTEGER, "
|
+ LONGITUDE + " INTEGER, "
|
||||||
+ LATITUDE + " INTEGER, "
|
+ LATITUDE + " INTEGER, "
|
||||||
+ TIME + " INTEGER, "
|
+ TIME + " INTEGER, "
|
||||||
+ ALTITUDE + " FLOAT, "
|
+ ALTITUDE + " FLOAT, "
|
||||||
+ HORIZONTAL_ACCURACY + " FLOAT, "
|
+ HORIZONTAL_ACCURACY + " FLOAT, "
|
||||||
+ BEARING + " FLOAT, "
|
+ BEARING + " FLOAT, "
|
||||||
+ PHOTOURL + " TEXT, "
|
|
||||||
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.content.res.Resources;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -31,11 +30,13 @@ public enum TrackFileFormat {
|
|||||||
return new KMLTrackExporter(context, contentProviderUtils, false);
|
return new KMLTrackExporter(context, contentProviderUtils, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return "application/vnd.google-earth.kml+xml";
|
return "application/vnd.google-earth.kml+xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return "kml";
|
return "kml";
|
||||||
}
|
}
|
||||||
@@ -50,11 +51,13 @@ public enum TrackFileFormat {
|
|||||||
return new KMZTrackExporter(context, contentProviderUtils, exportPhotos);
|
return new KMZTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return "application/vnd.google-earth.kmz";
|
return "application/vnd.google-earth.kmz";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return "kmz";
|
return "kmz";
|
||||||
}
|
}
|
||||||
@@ -72,11 +75,13 @@ public enum TrackFileFormat {
|
|||||||
return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
|
return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return "application/gpx+xml";
|
return "application/gpx+xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return "gpx";
|
return "gpx";
|
||||||
}
|
}
|
||||||
@@ -88,11 +93,13 @@ public enum TrackFileFormat {
|
|||||||
return new CSVTrackExporter(contentProviderUtils);
|
return new CSVTrackExporter(contentProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
return "text/csv";
|
return "text/csv";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return "csv";
|
return "csv";
|
||||||
@@ -115,16 +122,10 @@ public enum TrackFileFormat {
|
|||||||
return preferenceIdLabelMap;
|
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.
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
public abstract String getMimeType();
|
public abstract String getMimeType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,6 +138,7 @@ public enum TrackFileFormat {
|
|||||||
/**
|
/**
|
||||||
* Returns the file extension for each format.
|
* Returns the file extension for each format.
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
public abstract String getExtension();
|
public abstract String getExtension();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,6 +151,7 @@ public enum TrackFileFormat {
|
|||||||
/**
|
/**
|
||||||
* The identifier to be stored in the preferences.
|
* The identifier to be stored in the preferences.
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
public String getPreferenceId() {
|
public String getPreferenceId() {
|
||||||
return preferenceId;
|
return preferenceId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,8 +167,11 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
createExportTasks(allInOneFile);
|
if (createExportTasks(allInOneFile)) {
|
||||||
nextExport(null);
|
nextExport(null);
|
||||||
|
} else {
|
||||||
|
onExportEnded();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}).start();
|
}).start();
|
||||||
} else {
|
} else {
|
||||||
@@ -217,8 +220,12 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
exportTasks.clear();
|
exportTasks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createExportTasks(boolean allInOneFile) {
|
private boolean createExportTasks(boolean allInOneFile) {
|
||||||
List<Track> tracks = contentProviderUtils.getTracks();
|
List<Track> tracks = contentProviderUtils.getTracks();
|
||||||
|
if (tracks.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
exportTasks = new ArrayList<>();
|
exportTasks = new ArrayList<>();
|
||||||
if (allInOneFile) {
|
if (allInOneFile) {
|
||||||
String filename = "OpenTracks-Backup";
|
String filename = "OpenTracks-Backup";
|
||||||
@@ -227,6 +234,7 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.id()))).toList());
|
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.id()))).toList());
|
||||||
}
|
}
|
||||||
trackExportTotalCount = exportTasks.size();
|
trackExportTotalCount = exportTasks.size();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -391,7 +399,7 @@ public class ExportActivity extends AbstractActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to resolve the conflict if user gave the info needed.
|
* Try to resolve the conflict if user gave the info needed.
|
||||||
* Otherwise shows the buttons and views needed to gives the user the possibility of take a decision.
|
* Otherwise, shows the buttons and views needed to gives the user the possibility of take a decision.
|
||||||
*
|
*
|
||||||
* @return true if it could resolve the conflict or false otherwise.
|
* @return true if it could resolve the conflict or false otherwise.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package de.dennisguse.opentracks.publicapi;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||||
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
|
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||||
|
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||||
|
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link Intent} to request showing tracks on a Map or a Dashboard.
|
||||||
|
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
|
||||||
|
*/
|
||||||
|
public class DataProvider {
|
||||||
|
|
||||||
|
private static final String TAG = DataProvider.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
|
||||||
|
|
||||||
|
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assume "1" if not present.
|
||||||
|
*/
|
||||||
|
private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* version 1: the initial version.
|
||||||
|
* version 2: replaced pause/resume trackpoints for track segmentation (lat=100 / lat=200) by TrackPoint.Type.
|
||||||
|
* version 3: renamed track database columns.
|
||||||
|
*/
|
||||||
|
private static final int CURRENT_VERSION = 2;
|
||||||
|
|
||||||
|
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
||||||
|
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||||
|
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
||||||
|
private static final String EXTRAS_SHOW_FULLSCREEN = "EXTRAS_SHOULD_FULLSCREEN";
|
||||||
|
|
||||||
|
private static final int TRACK_URI_INDEX = 0;
|
||||||
|
private static final int TRACKPOINTS_URI_INDEX = 1;
|
||||||
|
private static final int MARKERS_URI_INDEX = 2;
|
||||||
|
|
||||||
|
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKS = Map.ofEntries(
|
||||||
|
//V2
|
||||||
|
Map.entry("_id", TracksColumns._ID),
|
||||||
|
Map.entry("name", TracksColumns.NAME),
|
||||||
|
Map.entry("description", TracksColumns.DESCRIPTION),
|
||||||
|
Map.entry("category", TracksColumns.ACTIVITY_TYPE + " AS category"),
|
||||||
|
Map.entry("activity_type_localized", TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||||
|
Map.entry("starttime", TracksColumns.STARTTIME + " AS starttime"),
|
||||||
|
Map.entry("stoptime", TracksColumns.STOPTIME + " AS stoptime"),
|
||||||
|
Map.entry("totaltime", TracksColumns.TOTALTIME + " AS totaltime"),
|
||||||
|
Map.entry("movingtime", TracksColumns.MOVINGTIME + " AS movingtime"),
|
||||||
|
Map.entry("totaldistance", TracksColumns.TOTALDISTANCE + " AS totaldistance"),
|
||||||
|
Map.entry("maxspeed", TracksColumns.MAXSPEED + " AS maxspeed"),
|
||||||
|
Map.entry("avgspeed", "null AS avgspeed"),
|
||||||
|
Map.entry("avgmovingspeed", "null AS avgmovingspeed"),
|
||||||
|
Map.entry("minelevation", TracksColumns.MIN_ALTITUDE + " AS minelevation"),
|
||||||
|
Map.entry("maxelevation", TracksColumns.MAX_ALTITUDE + " AS maxelevation"),
|
||||||
|
Map.entry("elevationgain", TracksColumns.ALTITUDE_GAIN + " AS elevationgain"),
|
||||||
|
Map.entry("elevationloss", TracksColumns.ALTITUDE_LOSS + " AS elevationloss"),
|
||||||
|
|
||||||
|
//V3
|
||||||
|
// Map.entry("_id", TracksColumns._ID),
|
||||||
|
// Map.entry("name",TracksColumns.NAME),
|
||||||
|
// Map.entry("description",TracksColumns.DESCRIPTION),
|
||||||
|
Map.entry("activity_type", TracksColumns.ACTIVITY_TYPE),
|
||||||
|
// Map.entry("activity_type_localized",TracksColumns.ACTIVITY_TYPE_LOCALIZED),
|
||||||
|
Map.entry("time_start", TracksColumns.STARTTIME),
|
||||||
|
Map.entry("time_stop", TracksColumns.STOPTIME),
|
||||||
|
Map.entry("duration_total", TracksColumns.TOTALTIME),
|
||||||
|
Map.entry("duration_moving", TracksColumns.MOVINGTIME),
|
||||||
|
Map.entry("distance", TracksColumns.TOTALDISTANCE),
|
||||||
|
Map.entry("speed_max", TracksColumns.MAXSPEED),
|
||||||
|
Map.entry("altitude_min", TracksColumns.MIN_ALTITUDE),
|
||||||
|
Map.entry("altitude_max", TracksColumns.MAX_ALTITUDE),
|
||||||
|
Map.entry("altitude_gain", TracksColumns.ALTITUDE_GAIN),
|
||||||
|
Map.entry("altitude_loss", TracksColumns.ALTITUDE_LOSS)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final Map<String, String> DATA_PROJECTIONMAP_TRACKPOINTS = Map.ofEntries(
|
||||||
|
//V2 and V3
|
||||||
|
Map.entry("_id", TrackPointsColumns._ID),
|
||||||
|
Map.entry("trackid", TrackPointsColumns.TRACKID),
|
||||||
|
Map.entry("latitude", TrackPointsColumns.LATITUDE),
|
||||||
|
Map.entry("longitude", TrackPointsColumns.LONGITUDE),
|
||||||
|
Map.entry("time", TrackPointsColumns.TIME),
|
||||||
|
Map.entry("type", TrackPointsColumns.TYPE),
|
||||||
|
Map.entry("speed", TrackPointsColumns.SPEED)
|
||||||
|
);
|
||||||
|
public static final Map<String, String> DATA_PROJECTIONMAP_MARKERS = Map.ofEntries(
|
||||||
|
//V2
|
||||||
|
Map.entry("_id", MarkerColumns._ID),
|
||||||
|
Map.entry("trackid", MarkerColumns.TRACKID),
|
||||||
|
Map.entry("name", MarkerColumns.NAME),
|
||||||
|
Map.entry("description", MarkerColumns.DESCRIPTION),
|
||||||
|
Map.entry("category", MarkerColumns.TYPE_LOCALIZED + " AS category"),
|
||||||
|
Map.entry("latitude", MarkerColumns.LATITUDE),
|
||||||
|
Map.entry("longitude", MarkerColumns.LONGITUDE),
|
||||||
|
Map.entry("photoUrl", MarkerColumns.PHOTOURL),
|
||||||
|
Map.entry("icon", "null AS icon"),
|
||||||
|
|
||||||
|
//V3
|
||||||
|
// Map.entry("_id", MarkerColumns._ID),
|
||||||
|
// Map.entry("trackid",MarkerColumns.TRACKID),
|
||||||
|
// Map.entry("name",MarkerColumns.NAME),
|
||||||
|
// Map.entry("description",MarkerColumns.DESCRIPTION),
|
||||||
|
Map.entry("marker_type_localized", MarkerColumns.TYPE_LOCALIZED)
|
||||||
|
// Map.entry("latitude",MarkerColumns.LATITUDE),
|
||||||
|
// Map.entry("longitude",MarkerColumns.LONGITUDE),
|
||||||
|
// Map.entry("photoUrl",MarkerColumns.PHOTOURL)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
private DataProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @param isRecording are we currently recording?
|
||||||
|
* @param trackIds the track ids
|
||||||
|
*/
|
||||||
|
public static void showTrackOnMap(Context context, boolean isRecording, Track.Id... trackIds) {
|
||||||
|
startDashboard(context, isRecording, null, null, trackIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
||||||
|
* By providing a targetPackage and targetClass an explicit intent can be sent,
|
||||||
|
* thus bypassing the need for the user to select an app.
|
||||||
|
*
|
||||||
|
* @param context the context
|
||||||
|
* @param isRecording are we currently recording?
|
||||||
|
* @param targetPackage the target package
|
||||||
|
* @param targetClass the target class
|
||||||
|
* @param trackIds the track ids
|
||||||
|
*/
|
||||||
|
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
|
||||||
|
if (trackIds.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
||||||
|
|
||||||
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
|
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKS_BY_IDS.getUri(), trackIdList));
|
||||||
|
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getUri(), trackIdList));
|
||||||
|
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(CustomContentProvider.UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getUri(), trackIdList));
|
||||||
|
|
||||||
|
Intent intent = new Intent(ACTION_DASHBOARD);
|
||||||
|
intent.putExtra(EXTRAS_PROTOCOL_VERSION, CURRENT_VERSION);
|
||||||
|
|
||||||
|
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
||||||
|
|
||||||
|
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn());
|
||||||
|
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen());
|
||||||
|
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
||||||
|
if (isRecording) {
|
||||||
|
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen());
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
||||||
|
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
||||||
|
clipData.addItem(new ClipData.Item(uris.get(MARKERS_URI_INDEX)));
|
||||||
|
intent.setClipData(clipData);
|
||||||
|
|
||||||
|
if (targetPackage != null && targetClass != null) {
|
||||||
|
Log.i(TAG, "Starting dashboard activity with explicit intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||||
|
intent.setClassName(targetPackage, targetClass);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.e(TAG, "Dashboard not installed; cannot start it.");
|
||||||
|
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import de.dennisguse.opentracks.data.models.Track;
|
|||||||
import de.dennisguse.opentracks.data.models.TrackBuilder;
|
import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
|
||||||
|
|
||||||
public class StartRecording extends AbstractAPIActivity {
|
public class StartRecording extends AbstractAPIActivity {
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ public class StartRecording extends AbstractAPIActivity {
|
|||||||
String targetPackage = bundle.getString(EXTRA_STATS_TARGET_PACKAGE, null);
|
String targetPackage = bundle.getString(EXTRA_STATS_TARGET_PACKAGE, null);
|
||||||
String targetClass = bundle.getString(EXTRA_STATS_TARGET_CLASS, null);
|
String targetClass = bundle.getString(EXTRA_STATS_TARGET_CLASS, null);
|
||||||
if (targetClass != null && targetPackage != null) {
|
if (targetClass != null && targetPackage != null) {
|
||||||
IntentDashboardUtils.startDashboard(this, true, targetPackage, targetClass, trackId);
|
DataProvider.startDashboard(this, true, targetPackage, targetClass, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||||
|
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BarometerBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||||
|
*/
|
||||||
|
//TODO Rename class
|
||||||
|
public class AltitudeChangeHandler extends SensorHandler<AtmosphericPressure, AltitudeGainLoss> {
|
||||||
|
|
||||||
|
public AltitudeChangeHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverInternal() {
|
||||||
|
return new BarometerInternal(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new BarometerBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public AggregatorBarometer createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorBarometer(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_pressure_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
|
|
||||||
private static final String TAG = BluetoothHandlerCyclingCadence.class.getSimpleName();
|
|
||||||
|
|
||||||
public static final List<ServiceMeasurementUUID> CYCLING_CADENCE = List.of(
|
|
||||||
BluetoothHandlerCyclingPower.CYCLING_POWER,
|
|
||||||
BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ServiceMeasurementUUID> getServices() {
|
|
||||||
return CYCLING_CADENCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AggregatorCyclingCadence createEmptySensorData(String address, String name) {
|
|
||||||
return new AggregatorCyclingCadence(address, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingPower.CYCLING_POWER)) {
|
|
||||||
BluetoothHandlerCyclingPower.Data data = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
|
||||||
if (data != null && data.crank() != null) {
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), data.crank()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceMeasurementUUID.equals(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE)) {
|
|
||||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, CrankData> data = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
|
||||||
|
|
||||||
if (data != null && data.second != null) {
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), data.second));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Don't know how to decode this payload.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public record CrankData(
|
|
||||||
long crankRevolutionsCount, // UINT32
|
|
||||||
int crankRevolutionsTime // UINT16; 1/1024s
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import static de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure.ENVIRONMENTAL_SENSING_SERVICE;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Temperature;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerTemperature implements SensorHandlerInterface {
|
|
||||||
public static final ServiceMeasurementUUID TEMPERATURE = new ServiceMeasurementUUID(
|
|
||||||
ENVIRONMENTAL_SENSING_SERVICE,
|
|
||||||
new UUID(0x2A6E00001000L, 0x800000805f9b34fbL)
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ServiceMeasurementUUID> getServices() {
|
|
||||||
return List.of(TEMPERATURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
|
||||||
return new AggregatorTemperature(address, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
Temperature value = parseEnvironmentalSensing(characteristic);
|
|
||||||
if (value == null) return;
|
|
||||||
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decoding: org.bluetooth.characteristic.temperature.xml
|
|
||||||
*/
|
|
||||||
public static Temperature parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
|
||||||
byte[] raw = characteristic.getValue();
|
|
||||||
|
|
||||||
if (raw.length < 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 0);
|
|
||||||
return Temperature.ofCentiCelsius(pressure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Handler;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
|
|
||||||
* <p>
|
|
||||||
* Note: should only be instantiated once.
|
|
||||||
* TODO: listen for Bluetooth enabled/disabled events.
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class BluetoothRemoteSensorManager implements SensorConnector, SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Handler handler;
|
|
||||||
private boolean started = false;
|
|
||||||
|
|
||||||
private final BluetoothConnectionManager heartRate;
|
|
||||||
private final BluetoothConnectionManager temperature;
|
|
||||||
private final BluetoothConnectionManager cyclingCadence;
|
|
||||||
private final BluetoothConnectionManager cyclingSpeed;
|
|
||||||
private final BluetoothConnectionManager cyclingPower;
|
|
||||||
private final BluetoothConnectionManager runningSpeedAndCadence;
|
|
||||||
|
|
||||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
|
|
||||||
this.context = context;
|
|
||||||
this.handler = handler;
|
|
||||||
|
|
||||||
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
|
||||||
this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerHeartRate());
|
|
||||||
this.temperature = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerTemperature());
|
|
||||||
this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence());
|
|
||||||
this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed());
|
|
||||||
this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingPower());
|
|
||||||
this.runningSpeedAndCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerRunningSpeedAndCadence());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Context context, Handler handler) {
|
|
||||||
started = true;
|
|
||||||
|
|
||||||
// Triggers connection startup
|
|
||||||
onSharedPreferenceChanged(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void stop(Context context) {
|
|
||||||
heartRate.disconnect();
|
|
||||||
temperature.disconnect();
|
|
||||||
cyclingCadence.disconnect();
|
|
||||||
cyclingSpeed.disconnect();
|
|
||||||
cyclingPower.disconnect();
|
|
||||||
runningSpeedAndCadence.disconnect();
|
|
||||||
|
|
||||||
started = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
|
|
||||||
connectionManager.connect(context, handler, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences unused, @Nullable String key) {
|
|
||||||
if (!started) return;
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress();
|
|
||||||
connect(heartRate, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_temperature_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothTemperatureSensorAddress();
|
|
||||||
connect(temperature, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress();
|
|
||||||
connect(cyclingCadence, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress();
|
|
||||||
|
|
||||||
connect(cyclingSpeed, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
|
|
||||||
|
|
||||||
connect(cyclingPower, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
|
|
||||||
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress();
|
|
||||||
|
|
||||||
connect(runningSpeedAndCadence, address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||||
|
|
||||||
|
public class CyclingCadenceHandler extends SensorHandler<CyclingCadenceBluetooth.CrankData, Cadence> {
|
||||||
|
|
||||||
|
protected CyclingCadenceHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new CyclingCadenceBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorCyclingCadence createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorCyclingCadence(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_cycling_cadence_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
|
public class CyclingDistanceSpeedHandler extends SensorHandler<CyclingDistanceSpeedBluetooth.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
||||||
|
|
||||||
|
protected CyclingDistanceSpeedHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new CyclingDistanceSpeedBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorCyclingDistanceSpeed createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorCyclingDistanceSpeed(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_cycling_speed_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataReceived(CyclingDistanceSpeedBluetooth.WheelData value) {
|
||||||
|
//TODO Fetch once and then listen for changes.
|
||||||
|
((AggregatorCyclingDistanceSpeed) aggregator).setWheelCircumference(PreferencesUtils.getWheelCircumference());
|
||||||
|
|
||||||
|
super.onDataReceived(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||||
|
|
||||||
|
public class CyclingPowerHandler extends SensorHandler<CyclingPowerBluetooth.Data, Power> {
|
||||||
|
|
||||||
|
protected CyclingPowerHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new CyclingPowerBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorCyclingPower createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorCyclingPower(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_cycling_power_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
|
|
||||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
|
||||||
*/
|
|
||||||
public class GainManager implements SensorConnector {
|
|
||||||
|
|
||||||
private static final String TAG = GainManager.class.getSimpleName();
|
|
||||||
|
|
||||||
|
|
||||||
private final SensorManager.SensorDataChangedObserver listener;
|
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> connect();
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private Handler handler;
|
|
||||||
private Driver driver;
|
|
||||||
|
|
||||||
public GainManager(SensorManager.SensorDataChangedObserver listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(Context context, Handler handler) {
|
|
||||||
this.context = context;
|
|
||||||
this.handler = handler;
|
|
||||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop(Context context) {
|
|
||||||
Log.d(TAG, "Stop");
|
|
||||||
this.context = null;
|
|
||||||
this.handler = null;
|
|
||||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
|
|
||||||
onDisconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connect() {
|
|
||||||
onDisconnect();
|
|
||||||
|
|
||||||
String address = PreferencesUtils.getBarometerSensorAddress();
|
|
||||||
switch (PreferencesUtils.getSensorType(address)) {
|
|
||||||
case NONE -> {
|
|
||||||
driver = null;
|
|
||||||
listener.onRemove(new AggregatorBarometer(null, null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case INTERNAL -> driver = new BarometerInternal(listener);
|
|
||||||
case REMOTE -> driver =
|
|
||||||
new BluetoothConnectionManager(
|
|
||||||
BluetoothUtils.getAdapter(context),
|
|
||||||
listener,
|
|
||||||
new BluetoothHandlerBarometricPressure()
|
|
||||||
);
|
|
||||||
default -> throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.connect(context, handler, address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDisconnect() {
|
|
||||||
if (driver == null) return;
|
|
||||||
|
|
||||||
driver.disconnect();
|
|
||||||
listener.onDisconnect(new AggregatorBarometer("GainManager", null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.Position;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS;
|
||||||
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
|
public class GpsHandler extends SensorHandler<Location, Position> {
|
||||||
|
|
||||||
|
private final String TAG = GpsHandler.class.getSimpleName();
|
||||||
|
|
||||||
|
//TODO Refactor to just pass information via SensorManager.
|
||||||
|
private TrackPointCreator trackPointCreator;
|
||||||
|
|
||||||
|
private GpsStatusManager gpsStatusManager;
|
||||||
|
private Distance thresholdHorizontalAccuracy;
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
|
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||||
|
if (gpsStatusManager != null) {
|
||||||
|
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
|
||||||
|
gpsStatusManager.onRecordingDistanceChanged(gpsMinDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
|
||||||
|
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(R.string.min_sampling_interval_key, key)) {
|
||||||
|
if (gpsStatusManager != null) {
|
||||||
|
gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval());
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public GpsHandler(SensorManager sensorManager, TrackPointCreator trackPointCreator) {
|
||||||
|
super(sensorManager);
|
||||||
|
this.trackPointCreator = trackPointCreator;
|
||||||
|
gpsStatusManager = new GpsStatusManager(sensorManager.getContext(), trackPointCreator::sendGpsStatus, sensorManager.getHandler());
|
||||||
|
|
||||||
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected(String sensorAddress, String sensorName) {
|
||||||
|
super.onConnected(sensorAddress, sensorName);
|
||||||
|
gpsStatusManager.onGpsEnabled();
|
||||||
|
gpsStatusManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnected() {
|
||||||
|
super.onDisconnected();
|
||||||
|
gpsStatusManager.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorDeactivated() {
|
||||||
|
super.onSensorDeactivated();
|
||||||
|
gpsStatusManager.onGpsDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataReceived(Location location) {
|
||||||
|
// Send each update to the status; please note that this TrackPoint is not stored.
|
||||||
|
Position position = Position.of(location, trackPointCreator.getNow());
|
||||||
|
gpsStatusManager.onNewTrackPoint(position);
|
||||||
|
|
||||||
|
//TODO We could move the following check into the AggregatorGPS
|
||||||
|
if (!position.hasValidLocation()) {
|
||||||
|
Log.w(TAG, "Ignore newTrackPoint. Location is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!position.fulfillsAccuracy(thresholdHorizontalAccuracy)) {
|
||||||
|
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDataReceived(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverInternal() {
|
||||||
|
return new GpsInternal(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorGPS createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorGPS(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_gps_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getSensorPreferenceDefaultValue() {
|
||||||
|
return SensorType.INTERNAL.getPreferenceValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
import androidx.core.location.LocationListenerCompat;
|
|
||||||
import androidx.core.location.LocationManagerCompat;
|
|
||||||
import androidx.core.location.LocationRequestCompat;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
|
||||||
import de.dennisguse.opentracks.data.models.Position;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
|
||||||
public class GpsManager implements SensorConnector, LocationListenerCompat, GpsStatusManager.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
private final String TAG = GpsManager.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
|
||||||
|
|
||||||
private TrackPointCreator trackPointCreator;
|
|
||||||
|
|
||||||
private SensorManager.SensorDataChangedObserver observer;
|
|
||||||
private Context context;
|
|
||||||
private Handler handler;
|
|
||||||
|
|
||||||
private LocationManager locationManager;
|
|
||||||
private GpsStatusManager gpsStatusManager;
|
|
||||||
private Duration gpsInterval;
|
|
||||||
private Distance thresholdHorizontalAccuracy;
|
|
||||||
|
|
||||||
public GpsManager(TrackPointCreator trackPointCreator, SensorManager.SensorDataChangedObserver observer) {
|
|
||||||
this.trackPointCreator = trackPointCreator;
|
|
||||||
this.observer = observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(@NonNull Context context, @NonNull Handler handler) {
|
|
||||||
this.context = context;
|
|
||||||
this.handler = handler;
|
|
||||||
|
|
||||||
onSharedPreferenceChanged(null, null);
|
|
||||||
|
|
||||||
gpsStatusManager = new GpsStatusManager(context, this, handler);
|
|
||||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
|
|
||||||
observer.onConnect(new AggregatorGPS("internal"));
|
|
||||||
|
|
||||||
registerLocationListener();
|
|
||||||
gpsStatusManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStarted() {
|
|
||||||
return locationManager != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"MissingPermission"})
|
|
||||||
public void stop(Context context) {
|
|
||||||
if (isStarted()) {
|
|
||||||
LocationManagerCompat.removeUpdates(locationManager, this);
|
|
||||||
}
|
|
||||||
locationManager = null;
|
|
||||||
this.context = null;
|
|
||||||
handler = null;
|
|
||||||
|
|
||||||
gpsStatusManager.stop();
|
|
||||||
gpsStatusManager = null;
|
|
||||||
|
|
||||||
observer.onDisconnect(new AggregatorGPS("internal"));
|
|
||||||
observer = null;
|
|
||||||
|
|
||||||
trackPointCreator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
||||||
boolean registerListener = false;
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.min_sampling_interval_key, key)) {
|
|
||||||
registerListener = true;
|
|
||||||
|
|
||||||
gpsInterval = PreferencesUtils.getMinSamplingInterval();
|
|
||||||
|
|
||||||
if (gpsStatusManager != null) {
|
|
||||||
gpsStatusManager.onMinSamplingIntervalChanged(gpsInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
|
|
||||||
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
|
||||||
}
|
|
||||||
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
|
||||||
registerListener = true;
|
|
||||||
|
|
||||||
if (gpsStatusManager != null) {
|
|
||||||
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
|
|
||||||
gpsStatusManager.onRecordingDistanceChanged(gpsMinDistance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registerListener && isStarted()) {
|
|
||||||
registerLocationListener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if location is valid and builds a track point that will be send through TrackPointCreator.
|
|
||||||
*
|
|
||||||
* @param location {@link Location} object.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onLocationChanged(@NonNull Location location) {
|
|
||||||
// Send each update to the status; please note that this TrackPoint is not stored.
|
|
||||||
Position position = Position.of(location, trackPointCreator.createNow());
|
|
||||||
gpsStatusManager.onNewTrackPoint(position);
|
|
||||||
|
|
||||||
if (!position.hasValidLocation()) {
|
|
||||||
Log.w(TAG, "Ignore newTrackPoint. Location is invalid.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!position.fulfillsAccuracy(thresholdHorizontalAccuracy)) {
|
|
||||||
Log.d(TAG, "Ignore newTrackPoint. Poor accuracy.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(@NonNull String provider, int status, Bundle extras) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderEnabled(@NonNull String provider) {
|
|
||||||
gpsStatusManager.onGpsEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderDisabled(@NonNull String provider) {
|
|
||||||
gpsStatusManager.onGpsDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerLocationListener() {
|
|
||||||
if (!LocationManagerCompat.hasProvider(locationManager, LOCATION_PROVIDER)) {
|
|
||||||
Log.e(TAG, "Device doesn't have GPS.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(gpsInterval.toMillis())
|
|
||||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
|
||||||
.setMaxUpdateDelayMillis(0)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "Register for location updates " + context);
|
|
||||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, this);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGpsStatusChanged(GpsStatusValue currentStatus) {
|
|
||||||
trackPointCreator.sendGpsStatus(currentStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,7 +31,7 @@ class GpsStatusManager {
|
|||||||
private Duration signalLostThreshold;
|
private Duration signalLostThreshold;
|
||||||
|
|
||||||
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
|
||||||
private GpsStatusListener client;
|
private GpsStatusListener observer;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -44,8 +44,8 @@ class GpsStatusManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
public GpsStatusManager(Context context, GpsStatusListener client, Handler handler) {
|
public GpsStatusManager(Context context, GpsStatusListener observer, Handler handler) {
|
||||||
this.client = client;
|
this.observer = observer;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class GpsStatusManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
client.onGpsStatusChanged(GpsStatusValue.GPS_ENABLED);
|
observer.onGpsStatusChanged(GpsStatusValue.GPS_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,9 +62,7 @@ class GpsStatusManager {
|
|||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
stopTimer();
|
stopTimer();
|
||||||
client.onGpsStatusChanged(GpsStatusValue.GPS_NONE);
|
observer.onGpsStatusChanged(GpsStatusValue.GPS_NONE);
|
||||||
client = null;
|
|
||||||
handler = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,8 +157,8 @@ class GpsStatusManager {
|
|||||||
|
|
||||||
private void setGpsStatus(GpsStatusValue current) {
|
private void setGpsStatus(GpsStatusValue current) {
|
||||||
gpsStatus = current;
|
gpsStatus = current;
|
||||||
if (client != null) {
|
if (observer != null) {
|
||||||
client.onGpsStatusChanged(current);
|
observer.onGpsStatusChanged(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.HeartRateBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||||
|
|
||||||
|
public class HeartRateHandler extends SensorHandler<HeartRate, HeartRate> {
|
||||||
|
|
||||||
|
protected HeartRateHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new HeartRateBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorHeartRate createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorHeartRate(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_heart_rate_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||||
|
|
||||||
|
public class RunningSpeedCadenceDistanceHandler extends SensorHandler<RunningSpeedAndCadenceBluetooth.Data, AggregatorRunning.Data> {
|
||||||
|
|
||||||
|
protected RunningSpeedCadenceDistanceHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new RunningSpeedAndCadenceBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorRunning createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorRunning("", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_running_speed_and_cadence_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
|
|
||||||
public interface SensorConnector {
|
|
||||||
|
|
||||||
void start(Context context, Handler handler);
|
|
||||||
|
|
||||||
void stop(Context context);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.DriverObserver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implements DriverObserver<AggregatorInput> {
|
||||||
|
|
||||||
|
private static final String TAG = SensorHandler.class.getSimpleName();
|
||||||
|
|
||||||
|
private final SensorManager sensorManager;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public Driver driver;
|
||||||
|
|
||||||
|
protected Aggregator<AggregatorInput, AggregatorOutput> aggregator;
|
||||||
|
|
||||||
|
protected SensorHandler(SensorManager sensorManager) {
|
||||||
|
this.sensorManager = sensorManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect() {
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
String address = PreferencesUtils.getString(getSensorPreferenceKey(), getSensorPreferenceDefaultValue());
|
||||||
|
|
||||||
|
switch (PreferencesUtils.getSensorType(address)) {
|
||||||
|
case NONE -> {
|
||||||
|
onDisconnected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case INTERNAL -> {
|
||||||
|
driver = createDriverInternal();
|
||||||
|
}
|
||||||
|
case REMOTE -> {
|
||||||
|
driver = createDriverBluetooth(address);
|
||||||
|
}
|
||||||
|
default -> throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.connect(sensorManager.getContext(), sensorManager.getHandler(), address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect() {
|
||||||
|
if (driver == null) return;
|
||||||
|
|
||||||
|
driver.disconnect();
|
||||||
|
driver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected Driver createDriverInternal() {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
throw new RuntimeException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected abstract Aggregator<AggregatorInput, AggregatorOutput> createAggregator(String sensorAddress, String sensorName);
|
||||||
|
|
||||||
|
protected abstract int getSensorPreferenceKey();
|
||||||
|
|
||||||
|
protected String getSensorPreferenceDefaultValue() {
|
||||||
|
return SensorType.NONE.getPreferenceValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context getContext() {
|
||||||
|
return sensorManager.getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetAggregated() {
|
||||||
|
if (aggregator != null) {
|
||||||
|
aggregator.resetAggregated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
SensorData<AggregatorOutput> getSensorData(Instant now) {
|
||||||
|
if (aggregator == null) return null;
|
||||||
|
if (!aggregator.hasReceivedData()) return null;
|
||||||
|
|
||||||
|
return aggregator.getAggregatedValueWithSensorName(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO REMOVE
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(Aggregator<AggregatorInput, AggregatorOutput> aggregator) {
|
||||||
|
this.aggregator = aggregator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onConnected(String sensorAddress, String sensorName) {
|
||||||
|
SensorHandler.this.aggregator = createAggregator(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionLost() {
|
||||||
|
if (SensorHandler.this.aggregator == null) {
|
||||||
|
Log.d(TAG, "Connection lost, but where not connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorHandler.this.aggregator.resetAggregated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataReceived(AggregatorInput value) {
|
||||||
|
if (aggregator == null) {
|
||||||
|
throw new RuntimeException("Received data while being disconnected.");
|
||||||
|
}
|
||||||
|
SensorHandler.this.aggregator.add(sensorManager.getNow(), value);
|
||||||
|
sensorManager.onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnected() {
|
||||||
|
SensorHandler.this.aggregator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,153 +6,321 @@ import android.os.Handler;
|
|||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
|
import de.dennisguse.opentracks.data.models.Position;
|
||||||
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
|
||||||
public class SensorManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class SensorManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
private static final String TAG = SensorManager.class.getSimpleName();
|
private static final String TAG = SensorManager.class.getSimpleName();
|
||||||
|
|
||||||
//TODO Should be final and not be visible for testing
|
private Context context;
|
||||||
@VisibleForTesting
|
|
||||||
public SensorDataAggregator sensorDataAggregator;
|
private Handler handler;
|
||||||
|
|
||||||
private PowerManager.WakeLock wakeLock;
|
private PowerManager.WakeLock wakeLock;
|
||||||
|
|
||||||
private final TrackPointCreator observer;
|
private final TrackPointCreator observer;
|
||||||
|
|
||||||
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
|
private HeartRateHandler heartRateHandler;
|
||||||
|
private TemperatureHandler temperatureHandler;
|
||||||
|
private CyclingCadenceHandler cyclingCadenceHandler;
|
||||||
|
private CyclingDistanceSpeedHandler cyclingDistanceSpeedHandler;
|
||||||
|
private CyclingPowerHandler cyclingPowerHandler;
|
||||||
|
private RunningSpeedCadenceDistanceHandler runningSpeedCadenceDistanceHandler;
|
||||||
|
|
||||||
@Override
|
private AltitudeChangeHandler altitudeChangeHandler;
|
||||||
public void onConnect(Aggregator<?, ?> aggregator) {
|
|
||||||
sensorDataAggregator.add(aggregator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private GpsHandler gpsHandler;
|
||||||
public void onChange(Raw<?> data) {
|
|
||||||
sensorDataAggregator.update(data);
|
|
||||||
observer.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnect(Aggregator<?, ?> aggregator) {
|
|
||||||
sensorDataAggregator.add(aggregator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRemove(Aggregator<?, ?> aggregator) {
|
|
||||||
sensorDataAggregator.remove(aggregator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Instant getNow() {
|
|
||||||
return observer.createNow();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private BluetoothRemoteSensorManager bluetoothSensorManager;
|
|
||||||
|
|
||||||
private GainManager altitudeSumManager;
|
|
||||||
|
|
||||||
private GpsManager gpsManager;
|
|
||||||
|
|
||||||
public SensorManager(TrackPointCreator observer) {
|
public SensorManager(TrackPointCreator observer) {
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
this.sensorDataAggregator = new SensorDataAggregator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(Context context, Handler handler) {
|
public void start(Context context, Handler handler) {
|
||||||
if (gpsManager != null) {
|
if (isStarted()) {
|
||||||
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
|
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
|
||||||
}
|
}
|
||||||
|
|
||||||
wakeLock = SystemUtils.acquireWakeLock(context, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(context, wakeLock);
|
||||||
|
this.context = context;
|
||||||
|
this.handler = handler;
|
||||||
|
|
||||||
gpsManager = new GpsManager(observer, listener);
|
gpsHandler = new GpsHandler(this, observer);
|
||||||
altitudeSumManager = new GainManager(listener);
|
altitudeChangeHandler = new AltitudeChangeHandler(this);
|
||||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
|
heartRateHandler = new HeartRateHandler(this);
|
||||||
|
temperatureHandler = new TemperatureHandler(this);
|
||||||
|
cyclingCadenceHandler = new CyclingCadenceHandler(this);
|
||||||
|
cyclingDistanceSpeedHandler = new CyclingDistanceSpeedHandler(this);
|
||||||
|
cyclingPowerHandler = new CyclingPowerHandler(this);
|
||||||
|
runningSpeedCadenceDistanceHandler = new RunningSpeedCadenceDistanceHandler(this);
|
||||||
|
|
||||||
onSharedPreferenceChanged(null, null);
|
onSharedPreferenceChanged(null, null);
|
||||||
|
|
||||||
gpsManager.start(context, handler);
|
|
||||||
altitudeSumManager.start(context, handler);
|
|
||||||
bluetoothSensorManager.start(context, handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop(Context context) {
|
public void stop() {
|
||||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||||
|
|
||||||
bluetoothSensorManager.stop(context);
|
heartRateHandler.disconnect();
|
||||||
bluetoothSensorManager = null;
|
heartRateHandler = null;
|
||||||
|
|
||||||
altitudeSumManager.stop(context);
|
temperatureHandler.disconnect();
|
||||||
altitudeSumManager = null;
|
temperatureHandler = null;
|
||||||
|
|
||||||
gpsManager.stop(context);
|
cyclingCadenceHandler.disconnect();
|
||||||
gpsManager = null;
|
cyclingCadenceHandler = null;
|
||||||
|
|
||||||
sensorDataAggregator.clear();
|
cyclingDistanceSpeedHandler.disconnect();
|
||||||
}
|
cyclingDistanceSpeedHandler = null;
|
||||||
|
|
||||||
public SensorDataSet getSensorDataSet(Instant now) {
|
cyclingPowerHandler.disconnect();
|
||||||
return sensorDataAggregator.getSensorData(now);
|
cyclingPowerHandler = null;
|
||||||
|
|
||||||
|
runningSpeedCadenceDistanceHandler.disconnect();
|
||||||
|
runningSpeedCadenceDistanceHandler = null;
|
||||||
|
|
||||||
|
altitudeChangeHandler.disconnect();
|
||||||
|
altitudeChangeHandler = null;
|
||||||
|
|
||||||
|
gpsHandler.disconnect();
|
||||||
|
gpsHandler = null;
|
||||||
|
|
||||||
|
context = null;
|
||||||
|
handler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
if (bluetoothSensorManager == null || altitudeSumManager == null) {
|
if (gpsHandler == null || altitudeChangeHandler == null) {
|
||||||
Log.d(TAG, "No recording running and no reset necessary.");
|
Log.d(TAG, "No recording running and no reset necessary.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sensorDataAggregator.reset();
|
|
||||||
|
Log.i(TAG, "Resetting data");
|
||||||
|
|
||||||
|
heartRateHandler.resetAggregated();
|
||||||
|
temperatureHandler.resetAggregated();
|
||||||
|
cyclingCadenceHandler.resetAggregated();
|
||||||
|
cyclingDistanceSpeedHandler.resetAggregated();
|
||||||
|
cyclingPowerHandler.resetAggregated();
|
||||||
|
runningSpeedCadenceDistanceHandler.resetAggregated();
|
||||||
|
altitudeChangeHandler.resetAggregated();
|
||||||
|
gpsHandler.resetAggregated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void onChanged(Raw<?> data) {
|
public GpsHandler getGpsHandler() {
|
||||||
listener.onChange(data);
|
return gpsHandler;
|
||||||
}
|
|
||||||
|
|
||||||
public GpsManager getGpsManager() {
|
|
||||||
return gpsManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public GainManager getAltitudeSumManager() {
|
public AltitudeChangeHandler getAltitudeChangeHandler() {
|
||||||
return altitudeSumManager;
|
return altitudeChangeHandler;
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setAltitudeSumManager(GainManager altitudeSumManager) {
|
|
||||||
this.altitudeSumManager = altitudeSumManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
|
||||||
if (gpsManager != null) {
|
if (!isStarted()) return;
|
||||||
gpsManager.onSharedPreferenceChanged(sharedPreferences, key);
|
|
||||||
bluetoothSensorManager.onSharedPreferenceChanged(sharedPreferences, key);
|
if (PreferencesUtils.isKey(gpsHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
gpsHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(altitudeChangeHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
altitudeChangeHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(heartRateHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
heartRateHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(temperatureHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
temperatureHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(cyclingCadenceHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
cyclingCadenceHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(cyclingDistanceSpeedHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
cyclingDistanceSpeedHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(cyclingCadenceHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
cyclingPowerHandler.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(runningSpeedCadenceDistanceHandler.getSensorPreferenceKey(), key)) {
|
||||||
|
runningSpeedCadenceDistanceHandler.connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SensorDataChangedObserver {
|
public Instant getNow() {
|
||||||
|
return observer.getNow();
|
||||||
|
}
|
||||||
|
|
||||||
void onConnect(Aggregator<?, ?> sensorData);
|
public Context getContext() {
|
||||||
void onChange(Raw<?> sensorData);
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
void onDisconnect(Aggregator<?, ?> sensorData);
|
public Handler getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
void onRemove(Aggregator<?, ?> sensorData);
|
private SensorData<Cadence> getCadence(Instant now) {
|
||||||
|
{
|
||||||
|
SensorData<Cadence> value = cyclingCadenceHandler.getSensorData(now);
|
||||||
|
if (value != null) {
|
||||||
|
return cyclingCadenceHandler.getSensorData(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Instant getNow();
|
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||||
|
if (value != null && value.data().cadence() != null) {
|
||||||
|
return new SensorData<>(value.data().cadence(), value.sensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SensorData<Distance> getDistance(Instant now) {
|
||||||
|
{
|
||||||
|
SensorData<AggregatorCyclingDistanceSpeed.Data> value = cyclingDistanceSpeedHandler.getSensorData(now);
|
||||||
|
if (value != null) {
|
||||||
|
return new SensorData<>(value.data().distanceOverall(), value.sensorNameOrAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||||
|
if (value != null) {
|
||||||
|
return new SensorData<>(value.data().distance(), value.sensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TOOD simplify?
|
||||||
|
private SensorData<Speed> getSpeed(Instant now) {
|
||||||
|
{
|
||||||
|
SensorData<AggregatorCyclingDistanceSpeed.Data> value = cyclingDistanceSpeedHandler.getSensorData(now);
|
||||||
|
if (value != null && value.data() != null && value.data().speed() != null) {
|
||||||
|
return new SensorData<>(value.data().speed(), value.sensorNameOrAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorData<AggregatorRunning.Data> value = runningSpeedCadenceDistanceHandler.getSensorData(now);
|
||||||
|
if (value != null && value.data() != null && value.data().speed() != null) {
|
||||||
|
return new SensorData<>(value.data().speed(), value.sensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChange() {
|
||||||
|
observer.onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SensorDataSet getSensorDataSet(Instant now) {
|
||||||
|
//We always need a Position with now
|
||||||
|
SensorData<Position> gpsSensorData = gpsHandler.getSensorData(now);
|
||||||
|
if (gpsSensorData != null && gpsSensorData.data() != null) {
|
||||||
|
gpsSensorData = new SensorData<>(gpsSensorData.data().with(now), gpsSensorData.sensorNameOrAddress());
|
||||||
|
} else {
|
||||||
|
gpsSensorData = new SensorData<>(Position.of(now), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SensorDataSet(
|
||||||
|
gpsSensorData,
|
||||||
|
getSpeed(now),
|
||||||
|
getDistance(now),
|
||||||
|
heartRateHandler.getSensorData(now),
|
||||||
|
temperatureHandler.getSensorData(now),
|
||||||
|
getCadence(now),
|
||||||
|
cyclingPowerHandler.getSensorData(now),
|
||||||
|
altitudeChangeHandler.getSensorData(now)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void clear() {
|
||||||
|
Log.i(TAG, "Removing all aggregators");
|
||||||
|
|
||||||
|
//TODO This is to ExportImportTest alive; refactor test.
|
||||||
|
if (this.altitudeChangeHandler != null) {
|
||||||
|
this.altitudeChangeHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (this.heartRateHandler != null) {
|
||||||
|
this.heartRateHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (this.temperatureHandler != null) {
|
||||||
|
this.temperatureHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (this.cyclingCadenceHandler != null) {
|
||||||
|
this.cyclingCadenceHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (cyclingDistanceSpeedHandler != null) {
|
||||||
|
cyclingDistanceSpeedHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (cyclingPowerHandler != null) {
|
||||||
|
cyclingPowerHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (runningSpeedCadenceDistanceHandler != null) {
|
||||||
|
runningSpeedCadenceDistanceHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
if (this.gpsHandler != null) {
|
||||||
|
this.gpsHandler.setAggregator(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStarted() {
|
||||||
|
return wakeLock != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(AggregatorHeartRate data) {
|
||||||
|
heartRateHandler.setAggregator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(@NonNull AggregatorTemperature data) {
|
||||||
|
temperatureHandler.setAggregator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(@NonNull AggregatorCyclingCadence data) {
|
||||||
|
cyclingCadenceHandler.setAggregator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(@NonNull AggregatorCyclingDistanceSpeed data) {
|
||||||
|
cyclingDistanceSpeedHandler.setAggregator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(@NonNull AggregatorCyclingPower data) {
|
||||||
|
cyclingPowerHandler.setAggregator(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setAggregator(@NonNull AggregatorRunning data) {
|
||||||
|
runningSpeedCadenceDistanceHandler.setAggregator(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.models.Temperature;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.BluetoothDriver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.Driver;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.TemperatureBluetooth;
|
||||||
|
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||||
|
|
||||||
|
public class TemperatureHandler extends SensorHandler<Temperature, Temperature> {
|
||||||
|
|
||||||
|
protected TemperatureHandler(SensorManager sensorManager) {
|
||||||
|
super(sensorManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Driver createDriverBluetooth(String address) {
|
||||||
|
return new BluetoothDriver<>(
|
||||||
|
BluetoothUtils.getAdapter(getContext()),
|
||||||
|
this,
|
||||||
|
new TemperatureBluetooth()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected AggregatorTemperature createAggregator(String sensorAddress, String sensorName) {
|
||||||
|
return new AggregatorTemperature(sensorAddress, sensorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getSensorPreferenceKey() {
|
||||||
|
return R.string.settings_sensor_bluetooth_temperature_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-16
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
@@ -6,12 +6,9 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface {
|
public class BarometerBluetooth implements BluetoothDriver.BluetoothParser<AtmosphericPressure> {
|
||||||
static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL);
|
static final UUID ENVIRONMENTAL_SENSING_SERVICE = new UUID(0x181A00001000L, 0x800000805f9b34fbL);
|
||||||
public static final ServiceMeasurementUUID BAROMETRIC_PRESSURE = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID BAROMETRIC_PRESSURE = new ServiceMeasurementUUID(
|
||||||
ENVIRONMENTAL_SENSING_SERVICE,
|
ENVIRONMENTAL_SENSING_SERVICE,
|
||||||
@@ -24,16 +21,8 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
|
public AtmosphericPressure parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
return new AggregatorBarometer(address, name);
|
return parseEnvironmentalSensing(characteristic);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
AtmosphericPressure value = parseEnvironmentalSensing(characteristic);
|
|
||||||
if (value == null) return;
|
|
||||||
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,9 +12,6 @@ import androidx.annotation.NonNull;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
|
|
||||||
public class BarometerInternal implements Driver {
|
public class BarometerInternal implements Driver {
|
||||||
|
|
||||||
@@ -22,9 +19,9 @@ public class BarometerInternal implements Driver {
|
|||||||
|
|
||||||
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
|
||||||
|
|
||||||
private final SensorManager.SensorDataChangedObserver observer;
|
private final DriverObserver<AtmosphericPressure> observer;
|
||||||
|
|
||||||
private Context context;
|
private android.hardware.SensorManager sensorService;
|
||||||
|
|
||||||
private final SensorEventListener sensorEventListener = new SensorEventListener() {
|
private final SensorEventListener sensorEventListener = new SensorEventListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -34,7 +31,7 @@ public class BarometerInternal implements Driver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), AtmosphericPressure.ofHPA(event.values[0])));
|
observer.onDataReceived(AtmosphericPressure.ofHPA(event.values[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -43,40 +40,34 @@ public class BarometerInternal implements Driver {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public BarometerInternal(@NonNull SensorManager.SensorDataChangedObserver observer) {
|
public BarometerInternal(@NonNull DriverObserver<AtmosphericPressure> observer) {
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connect(Context context, Handler handler, String addressIgnored) {
|
public void connect(Context context, Handler handler, String addressIgnored) {
|
||||||
|
sensorService = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
Sensor pressureSensor = sensorService.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||||
|
|
||||||
|
if (pressureSensor != null && sensorService.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
||||||
|
observer.onConnected(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
|
||||||
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
|
||||||
if (pressureSensor == null) {
|
|
||||||
Log.w(TAG, "No pressure sensor available.");
|
Log.w(TAG, "No pressure sensor available.");
|
||||||
return;
|
observer.onDisconnected();
|
||||||
}
|
|
||||||
|
|
||||||
if (sensorManager.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
|
|
||||||
this.context = context;
|
|
||||||
observer.onConnect(new AggregatorBarometer("internal", null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return context != null;
|
return sensorService != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
if (!isConnected()) return;
|
if (!isConnected()) return;
|
||||||
|
|
||||||
android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
sensorService.unregisterListener(sensorEventListener);
|
||||||
sensorManager.unregisterListener(sensorEventListener);
|
sensorService = null;
|
||||||
this.context = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-47
@@ -14,7 +14,7 @@
|
|||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
@@ -31,23 +31,25 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.driver.Driver;
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||||
*/
|
*/
|
||||||
|
//TODO Re-act to Bluetooth disabled/enabled
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
public class BluetoothConnectionManager implements Driver {
|
public class BluetoothDriver<T> implements Driver {
|
||||||
|
|
||||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
private static final String TAG = BluetoothDriver.class.getSimpleName();
|
||||||
|
|
||||||
private final SensorManager.SensorDataChangedObserver observer;
|
private final DriverObserver<T> observer;
|
||||||
|
|
||||||
private final SensorHandlerInterface sensorHandler;
|
private final BluetoothParser<T> sensorHandler;
|
||||||
|
|
||||||
private final BluetoothAdapter bluetoothAdapter;
|
private final BluetoothAdapter bluetoothAdapter;
|
||||||
private BluetoothGatt bluetoothGatt;
|
private BluetoothGatt bluetoothGatt;
|
||||||
@@ -55,27 +57,29 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
@Override
|
@Override
|
||||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
case BluetoothProfile.STATE_CONNECTING ->
|
case BluetoothProfile.STATE_CONNECTING -> {
|
||||||
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
|
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
|
||||||
|
}
|
||||||
case BluetoothProfile.STATE_CONNECTED -> {
|
case BluetoothProfile.STATE_CONNECTED -> {
|
||||||
Log.i(TAG, gatt.getDevice() + ": connected to sensor; discovering services");
|
Log.i(TAG, gatt.getDevice() + ": connected to sensor; discovering services");
|
||||||
gatt.discoverServices();
|
gatt.discoverServices();
|
||||||
}
|
}
|
||||||
case BluetoothProfile.STATE_DISCONNECTING ->
|
case BluetoothProfile.STATE_DISCONNECTING -> Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
|
||||||
Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
|
|
||||||
case BluetoothProfile.STATE_DISCONNECTED -> {
|
case BluetoothProfile.STATE_DISCONNECTED -> {
|
||||||
//This is also triggered, if no connection was established (ca. 30s)
|
//This is also triggered, if no connection was established (ca. 30s)
|
||||||
Log.i(TAG, gatt.getDevice() + ": disconnected from sensor: trying to reconnect");
|
Log.i(TAG, gatt.getDevice() + ": disconnected from sensor: trying to reconnect");
|
||||||
if (gatt.connect()) {
|
if (gatt.connect()) {
|
||||||
Log.e(TAG, gatt.getDevice() + ": could not trigger reconnect for sensor");
|
Log.e(TAG, gatt.getDevice() + ": could not trigger reconnect for sensor");
|
||||||
}
|
}
|
||||||
clearData();
|
observer.onConnectionLost();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) {
|
||||||
|
BluetoothDevice device = gatt.getDevice();
|
||||||
|
|
||||||
BluetoothGattService gattService = null;
|
BluetoothGattService gattService = null;
|
||||||
ServiceMeasurementUUID serviceMeasurement = null;
|
ServiceMeasurementUUID serviceMeasurement = null;
|
||||||
for (ServiceMeasurementUUID s : sensorHandler.getServices()) {
|
for (ServiceMeasurementUUID s : sensorHandler.getServices()) {
|
||||||
@@ -87,13 +91,13 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gattService == null) {
|
if (gattService == null) {
|
||||||
Log.e(TAG, gatt.getDevice() + ": could not get gattService for serviceUUID=" + serviceMeasurement);
|
Log.e(TAG, device + ": could not get gattService for serviceUUID=" + serviceMeasurement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
|
||||||
if (characteristic == null) {
|
if (characteristic == null) {
|
||||||
Log.e(TAG, gatt.getDevice() + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
Log.e(TAG, device + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gatt.setCharacteristicNotification(characteristic, true);
|
gatt.setCharacteristicNotification(characteristic, true);
|
||||||
@@ -111,6 +115,8 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
if (!gatt.writeDescriptor(descriptor)) {
|
if (!gatt.writeDescriptor(descriptor)) {
|
||||||
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written");
|
Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observer.onConnected(device.getAddress(), device.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,11 +133,15 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensorHandler.handlePayload(observer, serviceMeasurementUUID.get(), gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic);
|
T value = sensorHandler.parsePayload(serviceMeasurementUUID.get(), gatt.getDevice().getName(), characteristic);
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
observer.onDataReceived(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BluetoothConnectionManager(BluetoothAdapter bluetoothAdapter, SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) {
|
public BluetoothDriver(BluetoothAdapter bluetoothAdapter, DriverObserver<T> observer, BluetoothParser<T> sensorHandler) {
|
||||||
this.bluetoothAdapter = bluetoothAdapter;
|
this.bluetoothAdapter = bluetoothAdapter;
|
||||||
this.observer = observer;
|
this.observer = observer;
|
||||||
this.sensorHandler = sensorHandler;
|
this.sensorHandler = sensorHandler;
|
||||||
@@ -144,26 +154,11 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SensorType.NONE.getPreferenceValue().equals(address)) {
|
|
||||||
Log.w(TAG, "NONE: going to disconnect");
|
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
disconnect();
|
Log.e(TAG, "Already connected; ignoring.");
|
||||||
observer.onRemove(sensorHandler.createEmptySensorData(null, null));
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isConnected()) {
|
|
||||||
Log.w(TAG, "Already connected; ignoring.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSameBluetoothDevice(address)) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothDevice device;
|
BluetoothDevice device;
|
||||||
try {
|
try {
|
||||||
device = bluetoothAdapter.getRemoteDevice(address);
|
device = bluetoothAdapter.getRemoteDevice(address);
|
||||||
@@ -175,37 +170,23 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
Log.d(TAG, device + ": trying to connect");
|
Log.d(TAG, device + ": trying to connect");
|
||||||
|
|
||||||
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
|
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
|
||||||
|
|
||||||
observer.onConnect(sensorHandler.createEmptySensorData(device.getAddress(), device.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void clearData() {
|
|
||||||
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void disconnect() {
|
public synchronized void disconnect() {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
Log.w(TAG, "Not connected; no need to re-connect.");
|
Log.w(TAG, "Not connected; cannot disconnect.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
|
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
|
||||||
bluetoothGatt.disconnect();
|
bluetoothGatt.disconnect();
|
||||||
bluetoothGatt.close();
|
bluetoothGatt.close();
|
||||||
clearData();
|
|
||||||
Log.i(TAG, bluetoothGatt.getDevice() + ": disconnect finished");
|
Log.i(TAG, bluetoothGatt.getDevice() + ": disconnect finished");
|
||||||
bluetoothGatt = null;
|
bluetoothGatt = null;
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized boolean isSameBluetoothDevice(String address) {
|
observer.onDisconnected();
|
||||||
if (bluetoothGatt == null) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return address.equals(bluetoothGatt.getDevice().getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isBluetoothEnabled() {
|
private boolean isBluetoothEnabled() {
|
||||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||||
}
|
}
|
||||||
@@ -214,4 +195,11 @@ public class BluetoothConnectionManager implements Driver {
|
|||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return bluetoothGatt != null;
|
return bluetoothGatt != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface BluetoothParser<T> {
|
||||||
|
|
||||||
|
List<ServiceMeasurementUUID> getServices();
|
||||||
|
|
||||||
|
T parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
|
|
||||||
|
public class CyclingCadenceBluetooth implements BluetoothDriver.BluetoothParser<CyclingCadenceBluetooth.CrankData> {
|
||||||
|
private static final String TAG = CyclingCadenceBluetooth.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final List<ServiceMeasurementUUID> CYCLING_CADENCE = List.of(
|
||||||
|
CyclingPowerBluetooth.CYCLING_POWER,
|
||||||
|
CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ServiceMeasurementUUID> getServices() {
|
||||||
|
return CYCLING_CADENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CrankData parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
|
if (serviceMeasurementUUID.equals(CyclingPowerBluetooth.CYCLING_POWER)) {
|
||||||
|
CyclingPowerBluetooth.Data data = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||||
|
if (data != null && data.crank() != null) {
|
||||||
|
return data.crank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceMeasurementUUID.equals(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE)) {
|
||||||
|
Pair<CyclingDistanceSpeedBluetooth.WheelData, CrankData> data = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||||
|
|
||||||
|
if (data != null && data.second != null) {
|
||||||
|
return data.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "Don't know how to decode this payload.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CrankData(
|
||||||
|
long crankRevolutionsCount, // UINT32
|
||||||
|
int crankRevolutionsTime // UINT16; 1/1024s
|
||||||
|
) {}
|
||||||
|
}
|
||||||
+13
-17
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -9,11 +9,9 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterface {
|
public class CyclingDistanceSpeedBluetooth implements BluetoothDriver.BluetoothParser<CyclingDistanceSpeedBluetooth.WheelData> {
|
||||||
|
|
||||||
public static final ServiceMeasurementUUID CYCLING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID CYCLING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||||
new UUID(0x181600001000L, 0x800000805f9b34fbL),
|
new UUID(0x181600001000L, 0x800000805f9b34fbL),
|
||||||
@@ -26,21 +24,18 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address, String name) {
|
public WheelData parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
return new AggregatorCyclingDistanceSpeed(address, name);
|
Pair<WheelData, CyclingCadenceBluetooth.CrankData> data = parseCyclingCrankAndWheel(characteristic);
|
||||||
|
if (data != null && data.first != null) {
|
||||||
|
return data.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return null;
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> data = parseCyclingCrankAndWheel(address, sensorName, characteristic);
|
|
||||||
if (data.first != null) {
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), data.first));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
public static Pair<WheelData, BluetoothHandlerCyclingCadence.CrankData> parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
public static Pair<WheelData, CyclingCadenceBluetooth.CrankData> parseCyclingCrankAndWheel(@NonNull BluetoothGattCharacteristic characteristic) {
|
||||||
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
||||||
int valueLength = characteristic.getValue().length;
|
int valueLength = characteristic.getValue().length;
|
||||||
if (valueLength == 0) {
|
if (valueLength == 0) {
|
||||||
@@ -61,13 +56,13 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
|||||||
index += 2;
|
index += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothHandlerCyclingCadence.CrankData crankData = null;
|
CyclingCadenceBluetooth.CrankData crankData = null;
|
||||||
if (hasCrank && valueLength - index >= 4) {
|
if (hasCrank && valueLength - index >= 4) {
|
||||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||||
index += 2;
|
index += 2;
|
||||||
|
|
||||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||||
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
|
crankData = new CyclingCadenceBluetooth.CrankData(crankCount, crankTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Pair<>(wheelData, crankData);
|
return new Pair<>(wheelData, crankData);
|
||||||
@@ -78,5 +73,6 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
|
|||||||
long wheelRevolutionsCount, // UINT32
|
long wheelRevolutionsCount, // UINT32
|
||||||
|
|
||||||
int wheelRevolutionsTime // UINT16; 1/1024s
|
int wheelRevolutionsTime // UINT16; 1/1024s
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+12
-20
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
@@ -9,11 +9,9 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
public class CyclingPowerBluetooth implements BluetoothDriver.BluetoothParser<CyclingPowerBluetooth.Data> {
|
||||||
|
|
||||||
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID CYCLING_POWER = new ServiceMeasurementUUID(
|
||||||
new UUID(0x181800001000L, 0x800000805f9b34fbL),
|
new UUID(0x181800001000L, 0x800000805f9b34fbL),
|
||||||
@@ -26,17 +24,8 @@ public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AggregatorCyclingPower createEmptySensorData(String address, String name) {
|
public CyclingPowerBluetooth.Data parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
return new AggregatorCyclingPower(address, name);
|
return parseCyclingPower(characteristic);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
Data cyclingPower = parseCyclingPower(characteristic);
|
|
||||||
|
|
||||||
if (cyclingPower != null) {
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), cyclingPower));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -69,19 +58,22 @@ public class BluetoothHandlerCyclingPower implements SensorHandlerInterface {
|
|||||||
index += 2 + 2;
|
index += 2 + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothHandlerCyclingCadence.CrankData crankData = null;
|
CyclingCadenceBluetooth.CrankData crankData = null;
|
||||||
if (hasCrank && valueLength - index >= 4) {
|
if (hasCrank && valueLength - index >= 4) {
|
||||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||||
index += 2;
|
index += 2;
|
||||||
|
|
||||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||||
|
|
||||||
crankData = new BluetoothHandlerCyclingCadence.CrankData(crankCount, crankTime);
|
crankData = new CyclingCadenceBluetooth.CrankData(crankCount, crankTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Data(Power.of(instantaneousPower), crankData);
|
return new Data(Power.of(instantaneousPower), crankData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Data(Power power, BluetoothHandlerCyclingCadence.CrankData crank) {}
|
public record Data(
|
||||||
|
Power power,
|
||||||
|
CyclingCadenceBluetooth.CrankData crank
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,11 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
public interface Driver {
|
public interface Driver {
|
||||||
|
|
||||||
|
//TODO Address is optional for internal drivers. Move address to driver instantiation.
|
||||||
void connect(Context context, Handler handler, String address);
|
void connect(Context context, Handler handler, String address);
|
||||||
|
|
||||||
|
//TODO Remove from interface?
|
||||||
boolean isConnected();
|
boolean isConnected();
|
||||||
|
|
||||||
void disconnect();
|
void disconnect();
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
|
|
||||||
|
public interface DriverObserver<T> {
|
||||||
|
/**
|
||||||
|
* The Driver could connect to the sensor.
|
||||||
|
*/
|
||||||
|
void onConnected(String sensorAddress, String sensorName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sensor got temporarily disconnected, Driver should try to re-connect.
|
||||||
|
*/
|
||||||
|
void onConnectionLost();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Driver provided data from the sensor.
|
||||||
|
*/
|
||||||
|
void onDataReceived(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Driver was shutdown and no further data will be provided.
|
||||||
|
*/
|
||||||
|
//TODO check if really need this, because the driver was told to disconnect and then just signalizes that it finished this task.
|
||||||
|
void onDisconnected();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Driver detected that the sensor got deactivated.
|
||||||
|
* For example, the user deactivated the internal GPS.
|
||||||
|
*/
|
||||||
|
default void onSensorDeactivated() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.location.LocationListenerCompat;
|
||||||
|
import androidx.core.location.LocationManagerCompat;
|
||||||
|
import androidx.core.location.LocationRequestCompat;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||||
|
|
||||||
|
public class GpsInternal implements Driver {
|
||||||
|
|
||||||
|
private static final String TAG = GpsInternal.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public final LocationListenerCompat locationListenerCompat = new LocationListenerCompat() {
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(@NonNull Location location) {
|
||||||
|
driverObserver.onDataReceived(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(@NonNull String provider) {
|
||||||
|
driverObserver.onConnected(null, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(@NonNull String provider) {
|
||||||
|
driverObserver.onSensorDeactivated();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final DriverObserver<Location> driverObserver;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public LocationManager locationManager;
|
||||||
|
|
||||||
|
public GpsInternal(DriverObserver<Location> driverObserver) {
|
||||||
|
this.driverObserver = driverObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(Context context, Handler handler, String address) {
|
||||||
|
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
|
||||||
|
if (!LocationManagerCompat.hasProvider(locationManager, LOCATION_PROVIDER)) {
|
||||||
|
Log.e(TAG, "Device doesn't have GPS.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
||||||
|
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||||
|
.setMaxUpdateDelayMillis(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||||
|
try {
|
||||||
|
Log.i(TAG, "Register for location updates " + context);
|
||||||
|
driverObserver.onConnected(null, null);
|
||||||
|
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, locationListenerCompat);
|
||||||
|
return;
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driverObserver.onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"MissingPermission"})
|
||||||
|
@Override
|
||||||
|
public void disconnect() {
|
||||||
|
if (!isConnected()) return;
|
||||||
|
|
||||||
|
LocationManagerCompat.removeUpdates(locationManager, locationListenerCompat);
|
||||||
|
|
||||||
|
locationManager = null;
|
||||||
|
driverObserver.onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return locationManager != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-16
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
@@ -9,11 +9,9 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerHeartRate implements SensorHandlerInterface {
|
public class HeartRateBluetooth implements BluetoothDriver.BluetoothParser<HeartRate> {
|
||||||
|
|
||||||
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
||||||
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
||||||
@@ -36,17 +34,8 @@ public class BluetoothHandlerHeartRate implements SensorHandlerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AggregatorHeartRate createEmptySensorData(String address, String name) {
|
public HeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
return new AggregatorHeartRate(address, name);
|
return parseHeartRate(characteristic);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
HeartRate heartRate = parseHeartRate(characteristic);
|
|
||||||
|
|
||||||
if (heartRate != null) {
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), heartRate));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
+5
-13
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
@@ -11,11 +11,9 @@ import java.util.UUID;
|
|||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
|
||||||
import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface;
|
|
||||||
|
|
||||||
public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInterface {
|
public class RunningSpeedAndCadenceBluetooth implements BluetoothDriver.BluetoothParser<RunningSpeedAndCadenceBluetooth.Data> {
|
||||||
|
|
||||||
|
|
||||||
public static final ServiceMeasurementUUID RUNNING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
public static final ServiceMeasurementUUID RUNNING_SPEED_CADENCE = new ServiceMeasurementUUID(
|
||||||
@@ -29,14 +27,8 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AggregatorRunning createEmptySensorData(String address, String name) {
|
public Data parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
return new AggregatorRunning(address, name);
|
return parseRunningSpeedAndCadence(sensorName, characteristic);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, @NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
|
||||||
Data data = parseRunningSpeedAndCadence(sensorName, characteristic);
|
|
||||||
observer.onChange(new Raw<>(observer.getNow(), data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
+5
-3
@@ -1,8 +1,10 @@
|
|||||||
package de.dennisguse.opentracks.sensors;
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
public enum SensorType {
|
public enum SensorType {
|
||||||
NONE("NONE"),
|
NONE("NONE"), //TODO Use R.string.sensor_type_value_none
|
||||||
INTERNAL("INTERNAL"),
|
INTERNAL("INTERNAL"), //TODO Use R.string.sensor_type_value_none
|
||||||
|
|
||||||
|
//NOTE: preferenceValue of REMOTE should not be used anywhere.
|
||||||
REMOTE("*");
|
REMOTE("*");
|
||||||
|
|
||||||
private final String preferenceValue;
|
private final String preferenceValue;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package de.dennisguse.opentracks.sensors.driver;
|
||||||
|
|
||||||
|
import static de.dennisguse.opentracks.sensors.driver.BarometerBluetooth.ENVIRONMENTAL_SENSING_SERVICE;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Temperature;
|
||||||
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
|
|
||||||
|
public class TemperatureBluetooth implements BluetoothDriver.BluetoothParser<Temperature> {
|
||||||
|
public static final ServiceMeasurementUUID TEMPERATURE = new ServiceMeasurementUUID(
|
||||||
|
ENVIRONMENTAL_SENSING_SERVICE,
|
||||||
|
new UUID(0x2A6E00001000L, 0x800000805f9b34fbL)
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ServiceMeasurementUUID> getServices() {
|
||||||
|
return List.of(TEMPERATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Temperature parsePayload(ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, BluetoothGattCharacteristic characteristic) {
|
||||||
|
return parseEnvironmentalSensing(characteristic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoding: org.bluetooth.characteristic.temperature.xml
|
||||||
|
*/
|
||||||
|
public static Temperature parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) {
|
||||||
|
byte[] raw = characteristic.getValue();
|
||||||
|
|
||||||
|
if (raw.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer pressure = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 0);
|
||||||
|
return Temperature.ofCentiCelsius(pressure);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +1,74 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public abstract class Aggregator<Input, Output> {
|
public abstract class Aggregator<InputType, OutputType> {
|
||||||
|
|
||||||
private static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
private static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
|
||||||
|
|
||||||
protected Raw<Input> previous;
|
@Nullable
|
||||||
|
protected Raw<InputType> previous;
|
||||||
|
|
||||||
protected Output aggregatedValue;
|
//TODO Make private
|
||||||
|
protected OutputType output;
|
||||||
|
|
||||||
private final String sensorAddress;
|
private final String sensorAddress;
|
||||||
private final String sensorName;
|
private final String sensorName;
|
||||||
|
|
||||||
Aggregator(String sensorAddress) {
|
|
||||||
this(sensorAddress, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Aggregator(String sensorAddress, String sensorName) {
|
Aggregator(String sensorAddress, String sensorName) {
|
||||||
this.sensorAddress = sensorAddress;
|
this.sensorAddress = sensorAddress;
|
||||||
this.sensorName = sensorName;
|
this.sensorName = sensorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSensorNameOrAddress() {
|
private String getSensorNameOrAddress() {
|
||||||
return sensorName != null ? sensorName : sensorAddress;
|
return sensorName != null ? sensorName : sensorAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void add(Raw<Input> current) {
|
public final void add(Instant now, InputType current) {
|
||||||
computeValue(current);
|
Raw<InputType> next = new Raw<>(now, current);
|
||||||
previous = current;
|
computeValue(next);
|
||||||
|
previous = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void computeValue(Raw<Input> current);
|
protected abstract void computeValue(@NonNull Raw<InputType> current);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return did we process data from a sensor.
|
* @return did we process data from a sensor.
|
||||||
* NOTE: for some sensors this may require more than one measurement.
|
* NOTE: for some sensors this may require more than one measurement.
|
||||||
*/
|
*/
|
||||||
public boolean hasReceivedData() {
|
public boolean hasReceivedData() {
|
||||||
return aggregatedValue != null;
|
return output != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated //TODO Remove: for sensor there is no default value. Make getAggregatedValue() @Nullable
|
@Nullable
|
||||||
@NonNull
|
protected OutputType getAggregatedValue(Instant now) {
|
||||||
protected abstract Output getNoneValue();
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Output getAggregatedValue(Instant now) {
|
|
||||||
if (!hasReceivedData()) {
|
|
||||||
return getNoneValue();
|
|
||||||
}
|
|
||||||
if (isOutdated(now)) {
|
if (isOutdated(now)) {
|
||||||
resetImmediate();
|
resetOutdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
return aggregatedValue;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public SensorData<Output> getAggregatedValueWithSensorName(Instant now) {
|
public SensorData<OutputType> getAggregatedValueWithSensorName(Instant now) {
|
||||||
return new SensorData<>(getAggregatedValue(now), getSensorNameOrAddress());
|
return new SensorData<>(getAggregatedValue(now), getSensorNameOrAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset short-term (i.e., non-aggregated) values that were directly derived from sensor data.
|
* Reset short-term (i.e., non-aggregated) values that were directly derived from sensor data.
|
||||||
*/
|
*/
|
||||||
protected abstract void resetImmediate();
|
protected void resetOutdated() {
|
||||||
|
output = null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset long-term (i.e., aggregated) values (more than derived from previous SensorData) like overall distance.
|
* Reset long-term (i.e., aggregated) values (more than derived from previous SensorData) like overall distance.
|
||||||
*/
|
*/
|
||||||
public abstract void resetAggregated();
|
public void resetAggregated() {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the data recent considering the current time.
|
* Is the data recent considering the current time.
|
||||||
@@ -97,6 +92,12 @@ public abstract class Aggregator<Input, Output> {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "sensorAddress=" + sensorAddress + " data=" + aggregatedValue;
|
return "sensorAddress=" + sensorAddress + " data=" + output;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected record Raw<T>(
|
||||||
|
@NonNull Instant time,
|
||||||
|
@NonNull T value
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Altitude;
|
|
||||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||||
import de.dennisguse.opentracks.sensors.PressureSensorUtils;
|
import de.dennisguse.opentracks.sensors.PressureSensorUtils;
|
||||||
@@ -16,35 +15,30 @@ public class AggregatorBarometer extends Aggregator<AtmosphericPressure, Altitud
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(Raw<AtmosphericPressure> current) {
|
protected void computeValue(@NonNull Raw<AtmosphericPressure> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
lastAcceptedSensorValue = current.value();
|
lastAcceptedSensorValue = current.value();
|
||||||
aggregatedValue = getNoneValue();
|
output = new AltitudeGainLoss(0f, 0f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, previous.value(), current.value());
|
PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, previous.value(), current.value());
|
||||||
if (altitudeChange != null) {
|
if (altitudeChange != null) {
|
||||||
aggregatedValue = new AltitudeGainLoss(aggregatedValue.gain_m() + altitudeChange.getAltitudeGain_m(), aggregatedValue.loss_m() + altitudeChange.getAltitudeLoss_m());
|
if (output == null) {
|
||||||
|
output = new AltitudeGainLoss(0f, 0f);
|
||||||
|
}
|
||||||
|
output = new AltitudeGainLoss(output.gain_m() + altitudeChange.getAltitudeGain_m(), output.loss_m() + altitudeChange.getAltitudeLoss_m());
|
||||||
|
|
||||||
lastAcceptedSensorValue = altitudeChange.currentSensorValue();
|
lastAcceptedSensorValue = altitudeChange.currentSensorValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetImmediate() {
|
protected void resetOutdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetAggregated() {
|
public void resetAggregated() {
|
||||||
aggregatedValue = getNoneValue();
|
output = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected AltitudeGainLoss getNoneValue() {
|
|
||||||
return new AltitudeGainLoss(0f, 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Data(Altitude gain, Altitude loss) {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-20
@@ -7,10 +7,10 @@ import androidx.annotation.NonNull;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
|
||||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||||
|
|
||||||
public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCyclingCadence.CrankData, Cadence> {
|
public class AggregatorCyclingCadence extends Aggregator<CyclingCadenceBluetooth.CrankData, Cadence> {
|
||||||
|
|
||||||
private final String TAG = AggregatorCyclingCadence.class.getSimpleName();
|
private final String TAG = AggregatorCyclingCadence.class.getSimpleName();
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(Raw<BluetoothHandlerCyclingCadence.CrankData> current) {
|
protected void computeValue(@NonNull Raw<CyclingCadenceBluetooth.CrankData> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -32,32 +32,18 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
|||||||
}
|
}
|
||||||
if (timeDiff.isNegative()) {
|
if (timeDiff.isNegative()) {
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
aggregatedValue = null;
|
output = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO We have to treat with overflow according to the documentation: read https://codeberg.org/OpenTracksApp/OpenTracks/pulls/953#issuecomment-6466930
|
// TODO We have to treat with overflow according to the documentation: read https://codeberg.org/OpenTracksApp/OpenTracks/pulls/953#issuecomment-6466930
|
||||||
if (current.value().crankRevolutionsCount() < previous.value().crankRevolutionsCount()) {
|
if (current.value().crankRevolutionsCount() < previous.value().crankRevolutionsCount()) {
|
||||||
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
||||||
|
output = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long crankDiff = UintUtils.diff(current.value().crankRevolutionsCount(), previous.value().crankRevolutionsCount(), UintUtils.UINT32_MAX);
|
long crankDiff = UintUtils.diff(current.value().crankRevolutionsCount(), previous.value().crankRevolutionsCount(), UintUtils.UINT32_MAX);
|
||||||
aggregatedValue = Cadence.of(crankDiff, timeDiff);
|
output = Cadence.of(crankDiff, timeDiff);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void resetImmediate() {
|
|
||||||
aggregatedValue = getNoneValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetAggregated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Cadence getNoneValue() {
|
|
||||||
return Cadence.of(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-17
@@ -8,10 +8,10 @@ import java.time.Duration;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
|
||||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||||
|
|
||||||
public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerCyclingDistanceSpeed.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
public class AggregatorCyclingDistanceSpeed extends Aggregator<CyclingDistanceSpeedBluetooth.WheelData, AggregatorCyclingDistanceSpeed.Data> {
|
||||||
|
|
||||||
private final String TAG = AggregatorCyclingDistanceSpeed.class.getSimpleName();
|
private final String TAG = AggregatorCyclingDistanceSpeed.class.getSimpleName();
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData> current) {
|
protected void computeValue(@NonNull Raw<CyclingDistanceSpeedBluetooth.WheelData> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -35,43 +35,39 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
|||||||
}
|
}
|
||||||
if (timeDiff.isNegative()) {
|
if (timeDiff.isNegative()) {
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
aggregatedValue = null;
|
output = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.value().wheelRevolutionsCount() < previous.value().wheelRevolutionsCount()) {
|
if (current.value().wheelRevolutionsCount() < previous.value().wheelRevolutionsCount()) {
|
||||||
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
||||||
|
output = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long wheelDiff = UintUtils.diff(current.value().wheelRevolutionsCount(), previous.value().wheelRevolutionsCount(), UintUtils.UINT32_MAX);
|
long wheelDiff = UintUtils.diff(current.value().wheelRevolutionsCount(), previous.value().wheelRevolutionsCount(), UintUtils.UINT32_MAX);
|
||||||
|
|
||||||
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||||
Distance distanceOverall = distance;
|
Distance distanceOverall = distance;
|
||||||
if (aggregatedValue != null) {
|
if (output != null) {
|
||||||
distanceOverall = distance.plus(aggregatedValue.distanceOverall);
|
distanceOverall = distance.plus(output.distanceOverall);
|
||||||
}
|
}
|
||||||
Speed speed_mps = Speed.of(distance, timeDiff);
|
Speed speed_mps = Speed.of(distance, timeDiff);
|
||||||
aggregatedValue = new Data(distance, distanceOverall, speed_mps);
|
output = new Data(distance, distanceOverall, speed_mps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetImmediate() {
|
protected void resetOutdated() {
|
||||||
aggregatedValue = new Data(Distance.ZERO, aggregatedValue.distanceOverall, Speed.ZERO);
|
Distance overallDistance = output != null ? output.distanceOverall : Distance.ZERO;
|
||||||
|
output = new Data(Distance.ZERO, overallDistance, Speed.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetAggregated() {
|
public void resetAggregated() {
|
||||||
if (aggregatedValue != null) {
|
if (output != null) {
|
||||||
aggregatedValue = new Data(aggregatedValue.distance, Distance.ZERO, aggregatedValue.speed);
|
output = new Data(output.distance, Distance.ZERO, output.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Data getNoneValue() {
|
|
||||||
return new Data(Distance.ZERO, Distance.ZERO, Speed.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWheelCircumference(Distance wheelCircumference) {
|
public void setWheelCircumference(Distance wheelCircumference) {
|
||||||
this.wheelCircumference = wheelCircumference;
|
this.wheelCircumference = wheelCircumference;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-19
@@ -3,31 +3,16 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Power;
|
import de.dennisguse.opentracks.data.models.Power;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||||
|
|
||||||
public class AggregatorCyclingPower extends Aggregator<BluetoothHandlerCyclingPower.Data, Power> {
|
public class AggregatorCyclingPower extends Aggregator<CyclingPowerBluetooth.Data, Power> {
|
||||||
|
|
||||||
public AggregatorCyclingPower(String name, String address) {
|
public AggregatorCyclingPower(String name, String address) {
|
||||||
super(name, address);
|
super(name, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeValue(Raw<BluetoothHandlerCyclingPower.Data> current) {
|
public void computeValue(@NonNull Raw<CyclingPowerBluetooth.Data> current) {
|
||||||
this.aggregatedValue = current.value().power();
|
this.output = current.value().power();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void resetImmediate() {
|
|
||||||
aggregatedValue = getNoneValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetAggregated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Power getNoneValue() {
|
|
||||||
return Power.of(0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Position;
|
import de.dennisguse.opentracks.data.models.Position;
|
||||||
|
|
||||||
public class AggregatorGPS extends Aggregator<Position, Position> {
|
public class AggregatorGPS extends Aggregator<Location, Position> {
|
||||||
|
|
||||||
|
public AggregatorGPS(String sensorAddress, String sensorName) {
|
||||||
public AggregatorGPS(String sensorAddress) {
|
super(sensorAddress, sensorName);
|
||||||
super(sensorAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(Raw<Position> current) {
|
protected void computeValue(@NonNull Raw<Location> current) {
|
||||||
aggregatedValue = current.value();
|
output = Position.of(current.value(), current.time());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void resetImmediate() {
|
|
||||||
aggregatedValue = Position.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -28,21 +22,7 @@ public class AggregatorGPS extends Aggregator<Position, Position> {
|
|||||||
/*
|
/*
|
||||||
* GPS data is not an aggregated value, but for now we want to ensure to only save the data once.
|
* GPS data is not an aggregated value, but for now we want to ensure to only save the data once.
|
||||||
* The data is too large to save it more often than needed (i.e., duplicated values).
|
* The data is too large to save it more often than needed (i.e., duplicated values).
|
||||||
* TODO: this behavior can be changed if TrackRecordingManager.insertTrackPoint() would strip GPS data if it was already saved. This would simplify TrackPointCreator.createCurrentTrackPoint()
|
|
||||||
*/
|
*/
|
||||||
aggregatedValue = Position.empty();
|
resetOutdated();
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected Position getNoneValue() {
|
|
||||||
return Position.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Position getAggregatedValue(Instant now) {
|
|
||||||
return super.getAggregatedValue(now)
|
|
||||||
.with(now);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,24 +11,9 @@ public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void computeValue(Raw<HeartRate> current) {
|
protected void computeValue(@NonNull Raw<HeartRate> current) {
|
||||||
if (current.value().isValid()) {
|
if (current.value().isValid()) {
|
||||||
this.aggregatedValue = current.value();
|
this.output = current.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void resetImmediate() {
|
|
||||||
aggregatedValue = getNoneValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetAggregated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected HeartRate getNoneValue() {
|
|
||||||
return HeartRate.of(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import androidx.annotation.NonNull;
|
|||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
import de.dennisguse.opentracks.data.models.Cadence;
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
|
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
|
||||||
*/
|
*/
|
||||||
public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningSpeedAndCadence.Data, AggregatorRunning.Data> {
|
public final class AggregatorRunning extends Aggregator<RunningSpeedAndCadenceBluetooth.Data, AggregatorRunning.Data> {
|
||||||
|
|
||||||
private static final String TAG = AggregatorRunning.class.getSimpleName();
|
private static final String TAG = AggregatorRunning.class.getSimpleName();
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeValue(Raw<BluetoothHandlerRunningSpeedAndCadence.Data> current) {
|
public void computeValue(@NonNull Raw<RunningSpeedAndCadenceBluetooth.Data> current) {
|
||||||
if (previous == null) {
|
if (previous == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -27,33 +27,31 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
|
|||||||
Distance distance = null;
|
Distance distance = null;
|
||||||
if (previous.value().totalDistance() != null && current.value().totalDistance() != null) {
|
if (previous.value().totalDistance() != null && current.value().totalDistance() != null) {
|
||||||
distance = current.value().totalDistance().minus(previous.value().totalDistance());
|
distance = current.value().totalDistance().minus(previous.value().totalDistance());
|
||||||
if (aggregatedValue != null) {
|
if (output != null) {
|
||||||
distance = distance.plus(aggregatedValue.distance);
|
distance = distance.plus(output.distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregatedValue = new Data(current.value().speed(), current.value().cadence(), distance);
|
output = new Data(current.value().speed(), current.value().cadence(), distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void resetImmediate() {
|
protected void resetOutdated() {
|
||||||
aggregatedValue = new Data(Speed.ZERO, Cadence.of(0f), aggregatedValue.distance);
|
Distance distance = output != null ? output.distance : Distance.ZERO;
|
||||||
|
output = new Data(Speed.ZERO, Cadence.of(0f), distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetAggregated() {
|
public void resetAggregated() {
|
||||||
if (aggregatedValue != null) {
|
if (output != null) {
|
||||||
aggregatedValue = new Data(aggregatedValue.speed, aggregatedValue.cadence, Distance.ZERO);
|
output = new Data(output.speed, output.cadence, Distance.ZERO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
public record Data(
|
||||||
@Override
|
Speed speed,
|
||||||
protected Data getNoneValue() {
|
Cadence cadence,
|
||||||
return new Data(Speed.ZERO, Cadence.of(0f), Distance.ZERO);
|
@NonNull Distance distance) {
|
||||||
}
|
|
||||||
|
|
||||||
public record Data(Speed speed, Cadence cadence, @NonNull Distance distance) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-13
@@ -12,21 +12,10 @@ public class AggregatorTemperature extends Aggregator<Temperature, Temperature>
|
|||||||
super(sensorAddress, sensorName);
|
super(sensorAddress, sensorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void computeValue(Raw<Temperature> current) {
|
protected void computeValue(@NonNull Raw<Temperature> current) {
|
||||||
this.aggregatedValue = current.value();
|
this.output = current.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected Temperature getNoneValue() {
|
|
||||||
return Temperature.ofCentiCelsius(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void resetImmediate() {
|
|
||||||
aggregatedValue = getNoneValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetAggregated() {};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Duration getMaxSensorAge() {
|
protected Duration getMaxSensorAge() {
|
||||||
return Duration.ofMinutes(1);
|
return Duration.ofMinutes(1);
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public record Raw<T>(
|
|
||||||
@NonNull Instant time,
|
|
||||||
@NonNull T value
|
|
||||||
) {
|
|
||||||
public Raw(@NonNull Clock clock, @NonNull T value) {
|
|
||||||
this(clock.instant(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public Raw(@NonNull String time, @NonNull T value) {
|
|
||||||
this(Instant.parse(time), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
package de.dennisguse.opentracks.sensors.sensorData;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
public record SensorData<T>(
|
public record SensorData<T>(
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
T data,
|
T data,
|
||||||
|
|
||||||
String sensorNameOrAddress
|
String sensorNameOrAddress
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
|
||||||
import de.dennisguse.opentracks.data.models.Cadence;
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
|
||||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
|
||||||
import de.dennisguse.opentracks.data.models.Position;
|
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
|
||||||
import de.dennisguse.opentracks.data.models.Temperature;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
public class SensorDataAggregator {
|
|
||||||
|
|
||||||
private static final String TAG = SensorDataAggregator.class.getSimpleName();
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorHeartRate heartRate;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorTemperature temperature;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorCyclingCadence cyclingCadence;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorCyclingDistanceSpeed cyclingDistanceSpeed;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorCyclingPower cyclingPower;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorRunning runningDistanceSpeedCadence;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatorBarometer barometer;
|
|
||||||
|
|
||||||
public AggregatorGPS gps;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public SensorData<HeartRate> getHeartRate(Instant now) {
|
|
||||||
if (heartRate != null) {
|
|
||||||
SensorData<HeartRate> value = heartRate.getAggregatedValueWithSensorName(now);
|
|
||||||
if (value.data().isValid()) return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SensorData<Cadence> getCadence(Instant now) {
|
|
||||||
if (cyclingCadence != null) {
|
|
||||||
return cyclingCadence.getAggregatedValueWithSensorName(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).cadence() != null) {
|
|
||||||
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.cadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SensorData<Distance> getDistance(Instant now) {
|
|
||||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData()) {
|
|
||||||
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).distanceOverall(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData()) {
|
|
||||||
return new SensorData<>(runningDistanceSpeedCadence.getAggregatedValue(now).distance(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TOOD simplify?
|
|
||||||
private SensorData<Speed> getSpeed(Instant now) {
|
|
||||||
|
|
||||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasReceivedData() && cyclingDistanceSpeed.getAggregatedValue(now).speed() != null) {
|
|
||||||
return new SensorData<>(cyclingDistanceSpeed.getAggregatedValue(now).speed(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasReceivedData() && runningDistanceSpeedCadence.getAggregatedValue(now).speed() != null) {
|
|
||||||
return new SensorData<>(runningDistanceSpeedCadence.aggregatedValue.speed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(@NonNull Aggregator<?, ?> data) {
|
|
||||||
set(data, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO Refactor generic "fun" here: go explicit.
|
|
||||||
public void update(@NonNull Raw<?> data) {
|
|
||||||
Object value = data.value();
|
|
||||||
|
|
||||||
if (value instanceof HeartRate) {
|
|
||||||
this.heartRate.add((Raw<HeartRate>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value instanceof Temperature) {
|
|
||||||
this.temperature.add((Raw<Temperature>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value instanceof BluetoothHandlerCyclingCadence.CrankData) {
|
|
||||||
this.cyclingCadence.add((Raw<BluetoothHandlerCyclingCadence.CrankData>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof BluetoothHandlerCyclingDistanceSpeed.WheelData) {
|
|
||||||
this.cyclingDistanceSpeed.setWheelCircumference(PreferencesUtils.getWheelCircumference()); //TODO Fetch once and then listen for changes.
|
|
||||||
this.cyclingDistanceSpeed.add((Raw<BluetoothHandlerCyclingDistanceSpeed.WheelData>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof BluetoothHandlerRunningSpeedAndCadence.Data) {
|
|
||||||
this.runningDistanceSpeedCadence.add((Raw<BluetoothHandlerRunningSpeedAndCadence.Data>) data);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof BluetoothHandlerCyclingPower.Data) {
|
|
||||||
this.cyclingPower.add((Raw<BluetoothHandlerCyclingPower.Data>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof AtmosphericPressure) {
|
|
||||||
this.barometer.add((Raw<AtmosphericPressure>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof Position) {
|
|
||||||
this.gps.add((Raw<Position>) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(@NonNull Aggregator<?, ?> type) {
|
|
||||||
set(type, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
Log.i(TAG, "Removing all aggregators");
|
|
||||||
this.heartRate = null;
|
|
||||||
this.temperature = null;
|
|
||||||
this.cyclingCadence = null;
|
|
||||||
this.cyclingDistanceSpeed = null;
|
|
||||||
this.cyclingPower = null;
|
|
||||||
this.runningDistanceSpeedCadence = null;
|
|
||||||
this.barometer = null;
|
|
||||||
this.gps = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SensorDataSet getSensorData(Instant now) {
|
|
||||||
return new SensorDataSet(
|
|
||||||
gps != null && gps.hasReceivedData() ? gps.getAggregatedValueWithSensorName(now) : new SensorData<>(Position.of(now), ""),
|
|
||||||
getSpeed(now),
|
|
||||||
getDistance(now),
|
|
||||||
getHeartRate(now),
|
|
||||||
temperature != null && temperature.hasReceivedData() ? temperature.getAggregatedValueWithSensorName(now) : null,
|
|
||||||
getCadence(now),
|
|
||||||
cyclingPower != null && cyclingPower.hasReceivedData() ? cyclingPower.getAggregatedValueWithSensorName(now) : null,
|
|
||||||
barometer != null && barometer.hasReceivedData() ? barometer.getAggregatedValueWithSensorName(now) : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
Log.i(TAG, "Resetting data");
|
|
||||||
|
|
||||||
if (heartRate != null) heartRate.resetAggregated();
|
|
||||||
if (temperature != null) temperature.resetAggregated();
|
|
||||||
if (cyclingCadence != null) cyclingCadence.resetAggregated();
|
|
||||||
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.resetAggregated();
|
|
||||||
if (cyclingPower != null) cyclingPower.resetAggregated();
|
|
||||||
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.resetAggregated();
|
|
||||||
if (barometer != null) barometer.resetAggregated();
|
|
||||||
if (gps != null) gps.resetAggregated();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO Refactor generic "fun" here: go explicit.
|
|
||||||
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
|
|
||||||
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
|
|
||||||
|
|
||||||
if (type instanceof AggregatorHeartRate) {
|
|
||||||
heartRate = (AggregatorHeartRate) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorTemperature) {
|
|
||||||
temperature = (AggregatorTemperature) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorCyclingCadence) {
|
|
||||||
cyclingCadence = (AggregatorCyclingCadence) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorCyclingDistanceSpeed) {
|
|
||||||
cyclingDistanceSpeed = (AggregatorCyclingDistanceSpeed) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorCyclingPower) {
|
|
||||||
cyclingPower = (AggregatorCyclingPower) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorRunning) {
|
|
||||||
runningDistanceSpeedCadence = (AggregatorRunning) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorBarometer) {
|
|
||||||
barometer = (AggregatorBarometer) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type instanceof AggregatorGPS) {
|
|
||||||
gps = (AggregatorGPS) sensorData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.sensors.sensorData;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
|
||||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
|
||||||
|
|
||||||
public interface SensorHandlerInterface {
|
|
||||||
|
|
||||||
List<ServiceMeasurementUUID> getServices();
|
|
||||||
|
|
||||||
Aggregator<?, ?> createEmptySensorData(String address, String name);
|
|
||||||
|
|
||||||
void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic);
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,10 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
|||||||
*/
|
*/
|
||||||
public record RecordingData(
|
public record RecordingData(
|
||||||
Track track,
|
Track track,
|
||||||
|
/*
|
||||||
|
* The current view on the SensorData.
|
||||||
|
* May contain data from previous measurements that are not present in SensorDataSet.
|
||||||
|
*/
|
||||||
TrackPoint latestTrackPoint,
|
TrackPoint latestTrackPoint,
|
||||||
SensorDataSet sensorDataSet,
|
SensorDataSet sensorDataSet,
|
||||||
Statistics currentSegment) {
|
Statistics currentSegment) {
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
|
|
||||||
RecordingData getDataForUI() {
|
RecordingData getDataForUI() {
|
||||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
||||||
|
TrackPoint trackPoint = current.first;
|
||||||
|
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint);
|
||||||
|
|
||||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, current.first);
|
TrackPoint TrackPointWithEGM2008 = ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
ALTITUDE_CORRECTION_MANAGER.correctAltitude(context, current.first);
|
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
@@ -145,7 +145,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
|||||||
TrackBuilder trackBuilder = new TrackBuilder(track);
|
TrackBuilder trackBuilder = new TrackBuilder(track);
|
||||||
trackBuilder.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
trackBuilder.setStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||||
|
|
||||||
return new RecordingData(trackBuilder.getTrack(), current.first, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
|
return new RecordingData(trackBuilder.getTrack(), TrackPointWithEGM2008, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||||
|
|
||||||
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
@VisibleForTesting
|
||||||
|
public static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||||
|
|
||||||
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
|
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
|
||||||
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||||
@@ -185,14 +186,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startRecording() {
|
private void startRecording() {
|
||||||
// Update instance variables
|
|
||||||
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
|
||||||
|
|
||||||
startSensors();
|
startSensors();
|
||||||
|
|
||||||
voiceAnnouncementManager.start(trackRecordingManager.getTrackStatistics());
|
voiceAnnouncementManager.start(trackRecordingManager.getTrackStatistics());
|
||||||
|
|
||||||
notificationManager.onRecordingStarted(this, recordingStatus.trackId());
|
notificationManager.onRecordingStarted(this, recordingStatus.trackId());
|
||||||
|
|
||||||
|
// Update instance variables
|
||||||
|
updateRecordingData.run();
|
||||||
|
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryStartSensors() {
|
public void tryStartSensors() {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
sensorManager.stop(context);
|
sensorManager.stop();
|
||||||
this.context = null;
|
this.context = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized TrackPoint createSegmentStartManual() {
|
public synchronized TrackPoint createSegmentStartManual() {
|
||||||
return TrackPoint.createSegmentStartManualWithTime(createNow());
|
return TrackPoint.createSegmentStartManualWithTime(getNow());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized TrackPoint createSegmentEnd() {
|
public synchronized TrackPoint createSegmentEnd() {
|
||||||
@@ -89,7 +89,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) {
|
public Pair<TrackPoint, SensorDataSet> createCurrentTrackPoint(@Nullable TrackPoint lastTrackPointUISpeed, @Nullable TrackPoint lastTrackPointUIAltitude, @Nullable TrackPoint lastStoredTrackPointWithLocation) {
|
||||||
Instant now = createNow();
|
Instant now = getNow();
|
||||||
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
||||||
|
|
||||||
Position position = sensorDataSet.position().data();
|
Position position = sensorDataSet.position().data();
|
||||||
@@ -102,10 +102,10 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastTrackPointUISpeed != null)
|
if (lastTrackPointUISpeed != null)
|
||||||
position.with(lastTrackPointUISpeed.position().speed());
|
position = position.with(lastTrackPointUISpeed.position().speed());
|
||||||
|
|
||||||
if (lastTrackPointUIAltitude != null)
|
if (lastTrackPointUIAltitude != null)
|
||||||
position.with(lastTrackPointUIAltitude.position().altitude());
|
position = position.with(lastTrackPointUIAltitude.position().altitude());
|
||||||
|
|
||||||
TrackPoint currentTrackPoint = new TrackPoint(
|
TrackPoint currentTrackPoint = new TrackPoint(
|
||||||
null,
|
null,
|
||||||
@@ -123,7 +123,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TrackPoint createTrackPoint(TrackPoint.Type type) {
|
private TrackPoint createTrackPoint(TrackPoint.Type type) {
|
||||||
Instant now = createNow();
|
Instant now = getNow();
|
||||||
|
|
||||||
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet(now);
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant createNow() {
|
public Instant getNow() {
|
||||||
return Instant.now(clock);
|
return Instant.now(clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import de.dennisguse.opentracks.data.models.HeartRateZones;
|
|||||||
import de.dennisguse.opentracks.data.models.Track;
|
import de.dennisguse.opentracks.data.models.Track;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||||
import de.dennisguse.opentracks.sensors.SensorType;
|
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||||
@@ -253,10 +253,6 @@ public class PreferencesUtils {
|
|||||||
return isReportSpeed(track.activityTypeLocalized());
|
return isReportSpeed(track.activityTypeLocalized());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getBluetoothSensorAddressNone() {
|
|
||||||
return SensorType.NONE.getPreferenceValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SensorType getSensorType(String address) {
|
public static SensorType getSensorType(String address) {
|
||||||
return Arrays.stream(SensorType.values())
|
return Arrays.stream(SensorType.values())
|
||||||
.filter(it -> it.getPreferenceValue().equals(address))
|
.filter(it -> it.getPreferenceValue().equals(address))
|
||||||
@@ -264,39 +260,11 @@ public class PreferencesUtils {
|
|||||||
.orElse(SensorType.REMOTE);
|
.orElse(SensorType.REMOTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBarometerSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_pressure_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBluetoothHeartRateSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBluetoothTemperatureSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_temperature_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBluetoothCyclingCadenceSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBluetoothCyclingSpeedSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Distance getWheelCircumference() {
|
public static Distance getWheelCircumference() {
|
||||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
||||||
return Distance.ofMM(getInt(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT));
|
return Distance.ofMM(getInt(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBluetoothCyclingPowerSensorAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getBluetoothRunningSpeedAndCadenceAddress() {
|
|
||||||
return getString(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, getBluetoothSensorAddressNone());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getBluetoothFilterEnabled() {
|
public static boolean getBluetoothFilterEnabled() {
|
||||||
final boolean DEFAULT = resources.getBoolean(R.bool.settings_sensor_bluetooth_service_filter_enabled_default);
|
final boolean DEFAULT = resources.getBoolean(R.bool.settings_sensor_bluetooth_service_filter_enabled_default);
|
||||||
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
||||||
@@ -579,6 +547,7 @@ public class PreferencesUtils {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO Rename to getGpsMinSamplingInterval
|
||||||
public static Duration getMinSamplingInterval() {
|
public static Duration getMinSamplingInterval() {
|
||||||
final Duration MIN_SAMPLING_INTERVAL = getMinSamplingIntervalDefault();
|
final Duration MIN_SAMPLING_INTERVAL = getMinSamplingIntervalDefault();
|
||||||
return Duration.ofSeconds(getInt(R.string.min_sampling_interval_key, (int) MIN_SAMPLING_INTERVAL.getSeconds()));
|
return Duration.ofSeconds(getInt(R.string.min_sampling_interval_key, (int) MIN_SAMPLING_INTERVAL.getSeconds()));
|
||||||
@@ -778,7 +747,14 @@ public class PreferencesUtils {
|
|||||||
// We want to really clear settings now.
|
// We want to really clear settings now.
|
||||||
sharedPreferences.edit().clear().commit();
|
sharedPreferences.edit().clear().commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each setting file must be mentioned here.
|
||||||
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
|
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.settings_defaults, readAgain);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.settings_user_interface, readAgain);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.settings_sensors, readAgain);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.settings_import_export, readAgain);
|
||||||
|
PreferenceManager.setDefaultValues(context, R.xml.settings_public_api, readAgain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getDefaultExportDirectoryUri() {
|
public static Uri getDefaultExportDirectoryUri() {
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ import android.util.AttributeSet;
|
|||||||
|
|
||||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -27,6 +27,6 @@ public class BluetoothLeCyclingCadencePreference extends BluetoothLeSensorPrefer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerCyclingCadence.CYCLING_CADENCE);
|
return BluetoothLeSensorPreferenceDialog.newInstance(getKey(), CyclingCadenceBluetooth.CYCLING_CADENCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -29,6 +29,6 @@ public class BluetoothLeCyclingPowerPreference extends BluetoothLeSensorPreferen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingPower.CYCLING_POWER));
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(CyclingPowerBluetooth.CYCLING_POWER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -29,6 +29,6 @@ public class BluetoothLeCyclingSpeedPreference extends BluetoothLeSensorPreferen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE));
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package de.dennisguse.opentracks.settings.bluetooth;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.core.location.LocationManagerCompat;
|
||||||
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||||
|
|
||||||
|
public class BluetoothLeGpsPreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
|
public BluetoothLeGpsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeGpsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeGpsPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BluetoothLeGpsPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
|
boolean hasInternalSensor = LocationManagerCompat.hasProvider((LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE), GpsInternal.LOCATION_PROVIDER);
|
||||||
|
|
||||||
|
return BluetoothLeSensorPreferenceDialog
|
||||||
|
.newInstance(getKey(), List.of(), hasInternalSensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -5,7 +5,7 @@ import android.util.AttributeSet;
|
|||||||
|
|
||||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerHeartRate;
|
import de.dennisguse.opentracks.sensors.driver.HeartRateBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -27,6 +27,6 @@ public class BluetoothLeHeartRatePreference extends BluetoothLeSensorPreference
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), BluetoothHandlerHeartRate.HEART_RATE_SUPPORTING_DEVICES);
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), HeartRateBluetooth.HEART_RATE_SUPPORTING_DEVICES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -9,7 +9,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerBarometricPressure;
|
import de.dennisguse.opentracks.sensors.driver.BarometerBluetooth;
|
||||||
|
|
||||||
public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -36,6 +36,6 @@ public class BluetoothLePressurePreference extends BluetoothLeSensorPreference {
|
|||||||
boolean hasInternalSensor = pressureSensor != null;
|
boolean hasInternalSensor = pressureSensor != null;
|
||||||
|
|
||||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog
|
||||||
.newInstance(getKey(), List.of(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE), hasInternalSensor);
|
.newInstance(getKey(), List.of(BarometerBluetooth.BAROMETRIC_PRESSURE), hasInternalSensor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -29,6 +29,6 @@ public class BluetoothLeRunningSpeedAndCadencePreference extends BluetoothLeSens
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE));
|
return BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(getKey(), List.of(RunningSpeedAndCadenceBluetooth.RUNNING_SPEED_CADENCE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -10,6 +10,7 @@ import android.bluetooth.le.ScanResult;
|
|||||||
import android.bluetooth.le.ScanSettings;
|
import android.bluetooth.le.ScanSettings;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -18,6 +19,7 @@ import android.util.Log;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.DialogPreference;
|
import androidx.preference.DialogPreference;
|
||||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||||
@@ -28,8 +30,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||||
import de.dennisguse.opentracks.sensors.SensorType;
|
|
||||||
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
import de.dennisguse.opentracks.sensors.ServiceMeasurementUUID;
|
||||||
|
import de.dennisguse.opentracks.sensors.driver.SensorType;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||||
|
|
||||||
@@ -80,6 +82,12 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Object onGetDefaultValue(@NonNull TypedArray a, int index) {
|
||||||
|
return a.getString(index);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSetInitialValue(Object defaultValue) {
|
protected void onSetInitialValue(Object defaultValue) {
|
||||||
setValue(getPersistedString((String) defaultValue));
|
setValue(getPersistedString((String) defaultValue));
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ import androidx.preference.PreferenceDialogFragmentCompat;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerTemperature;
|
import de.dennisguse.opentracks.sensors.driver.TemperatureBluetooth;
|
||||||
|
|
||||||
public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreference {
|
public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreference {
|
||||||
|
|
||||||
@@ -30,6 +30,6 @@ public class BluetoothLeTemperaturePreference extends BluetoothLeSensorPreferenc
|
|||||||
@Override
|
@Override
|
||||||
public PreferenceDialogFragmentCompat createInstance() {
|
public PreferenceDialogFragmentCompat createInstance() {
|
||||||
return BluetoothLeSensorPreferenceDialog
|
return BluetoothLeSensorPreferenceDialog
|
||||||
.newInstance(getKey(), List.of(BluetoothHandlerTemperature.TEMPERATURE));
|
.newInstance(getKey(), List.of(TemperatureBluetooth.TEMPERATURE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -5,6 +5,7 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ public class RecordingLayoutIO {
|
|||||||
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
|
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
|
||||||
return CsvLayoutUtils.getCsvLines(csv).stream()
|
return CsvLayoutUtils.getCsvLines(csv).stream()
|
||||||
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
|
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
|
||||||
.toList();
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {
|
public static RecordingLayout fromCsvLine(@NonNull String csvLine, @NonNull Resources resources) {
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
|
||||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
|
||||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
|
||||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an {@link Intent} to request showing tracks on a Map or a Dashboard.
|
|
||||||
* The receiving {@link android.app.Activity} gets temporary access to the {@link TracksColumns} and the {@link TrackPointsColumns} (incl. update).
|
|
||||||
*/
|
|
||||||
public class IntentDashboardUtils {
|
|
||||||
|
|
||||||
private static final String TAG = IntentDashboardUtils.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final String ACTION_DASHBOARD = "Intent.OpenTracks-Dashboard";
|
|
||||||
|
|
||||||
private static final String ACTION_DASHBOARD_PAYLOAD = ACTION_DASHBOARD + ".Payload";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assume "v1" if not present.
|
|
||||||
*/
|
|
||||||
private static final String EXTRAS_PROTOCOL_VERSION = "PROTOCOL_VERSION";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* version 1: the initial version.
|
|
||||||
* version 2: replaced pause/resume trackpoints for track segmentation (lat=100 / lat=200) by TrackPoint.Type.
|
|
||||||
*/
|
|
||||||
private static final int CURRENT_VERSION = 2;
|
|
||||||
|
|
||||||
private static final String EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK = "EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK";
|
|
||||||
private static final String EXTRAS_SHOULD_KEEP_SCREEN_ON = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
|
||||||
private static final String EXTRAS_SHOW_WHEN_LOCKED = "EXTRAS_SHOULD_KEEP_SCREEN_ON";
|
|
||||||
private static final String EXTRAS_SHOW_FULLSCREEN = "EXTRAS_SHOULD_FULLSCREEN";
|
|
||||||
|
|
||||||
private static final int TRACK_URI_INDEX = 0;
|
|
||||||
private static final int TRACKPOINTS_URI_INDEX = 1;
|
|
||||||
private static final int MARKERS_URI_INDEX = 2;
|
|
||||||
private static final int NONE_SELECTED = -1;
|
|
||||||
|
|
||||||
private IntentDashboardUtils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param isRecording are we currently recording?
|
|
||||||
* @param trackIds the track ids
|
|
||||||
*/
|
|
||||||
public static void showTrackOnMap(Context context, boolean isRecording, Track.Id... trackIds) {
|
|
||||||
startDashboard(context, isRecording, null, null, trackIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send intent to show tracks on a map (needs an another app) as resource URIs.
|
|
||||||
* By providing a targetPackage and targetClass an explicit intent can be sent,
|
|
||||||
* thus bypassing the need for the user to select an app.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param isRecording are we currently recording?
|
|
||||||
* @param targetPackage the target package
|
|
||||||
* @param targetClass the target class
|
|
||||||
* @param trackIds the track ids
|
|
||||||
*/
|
|
||||||
public static void startDashboard(Context context, boolean isRecording, @Nullable String targetPackage, @Nullable String targetClass, Track.Id... trackIds) {
|
|
||||||
if (trackIds.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String trackIdList = ContentProviderUtils.formatIdListForUri(trackIds);
|
|
||||||
|
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
|
||||||
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
|
|
||||||
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
|
||||||
uris.add(MARKERS_URI_INDEX, Uri.withAppendedPath(MarkerColumns.CONTENT_URI_BY_TRACKID, trackIdList));
|
|
||||||
|
|
||||||
Intent intent = new Intent(ACTION_DASHBOARD);
|
|
||||||
intent.putExtra(EXTRAS_PROTOCOL_VERSION, CURRENT_VERSION);
|
|
||||||
|
|
||||||
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
|
|
||||||
|
|
||||||
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn());
|
|
||||||
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen());
|
|
||||||
intent.putExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK, isRecording);
|
|
||||||
if (isRecording) {
|
|
||||||
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen());
|
|
||||||
}
|
|
||||||
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
ClipData clipData = ClipData.newRawUri(null, uris.get(TRACK_URI_INDEX));
|
|
||||||
clipData.addItem(new ClipData.Item(uris.get(TRACKPOINTS_URI_INDEX)));
|
|
||||||
clipData.addItem(new ClipData.Item(uris.get(MARKERS_URI_INDEX)));
|
|
||||||
intent.setClipData(clipData);
|
|
||||||
|
|
||||||
if (targetPackage != null && targetClass != null) {
|
|
||||||
Log.i(TAG, "Starting dashboard activity with explicit intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
|
||||||
intent.setClassName(targetPackage, targetClass);
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "Starting dashboard activity with generic intent (package=" + targetPackage + ", class=" + targetClass + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.startActivity(intent);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
Log.e(TAG, "Dashboard not installed; cannot start it.");
|
|
||||||
Toast.makeText(context, R.string.show_on_dashboard_not_installed, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user