Compare commits

...

55 Commits

Author SHA1 Message Date
Dennis Guse 9f858168f8 Release: v3.21.0 2021-10-29 07:54:59 +02:00
Andrij Mizyk cfdcea2d88 Translated using Weblate (Ukrainian)
Currently translated at 92.6% (402 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-10-29 05:58:50 +02:00
Quang Trung 2d2db41c5d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-10-29 05:58:50 +02:00
Xosé M 2c73d0645a Translated using Weblate (Galician)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-10-29 05:58:50 +02:00
☆Verdulo ed1027eccc Translated using Weblate (Esperanto)
Currently translated at 92.0% (69 of 75 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/eo/
2021-10-29 05:58:49 +02:00
Dennis Guse 0dec17eba5 Bugfix: prevent NPE in onDestroy. 2021-10-29 05:58:22 +02:00
dependabot[bot] 774d701a09 Bump reactivecircus/android-emulator-runner from 2.20.0 to 2.21.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.20.0 to 2.21.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.20.0...v2.21.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-25 20:18:39 +02:00
Dennis Guse 23bcbf09a5 GPX importer supports speed without namespace (compatibility with other tools).
Fixed #774.
2021-10-20 21:50:51 +02:00
Dennis Guse 115966cbdf Bugfix: NPE using a cycling sensor. 2021-10-20 19:44:47 +02:00
Dennis Guse 1862bcc6f4 Cleanup. 2021-10-19 17:53:08 +02:00
Dennis Guse 900910e9f7 While recording: show cadence and speed from different sensor types correctly.
Fixes #970.
2021-10-19 17:53:08 +02:00
Dennis Guse 45571c68c9 Rename TrackPoint's cycling cadence to cadence as it may contain running data.
Part of #970.
2021-10-19 17:53:08 +02:00
Román Martínez 083b620e41 GPS data recording: remove idle/non-idle changes due to reported speed greater than 0.5mph.
This avoids that to storing GPS locations if the user r actually is not moving (likely bad GPS reception).

Fixes #968.

Co-authored-by: vlmendz <91810638+vlmendz@users.noreply.github.com>
Co-authored-by: Dennis Guse <dennis.guse@alumni.tu-berlin.de>
2021-10-18 23:26:07 +02:00
Dennis Guse 2f7a56bb17 Bugfix: importing files without speed resulted in moving time 0s and max speed of 0.
Fixes #982.
2021-10-18 18:41:53 +02:00
dependabot[bot] d8988663cb Bump actions/checkout from 2.3.4 to 2.3.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 12:42:34 +02:00
FTno c742dae6e0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (434 of 434 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-10-16 10:23:21 +02:00
FTno 292437e6bd Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-10-16 10:23:20 +02:00
J. Lavoie 8e9cde7222 Translated using Weblate (French)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-10-16 10:23:20 +02:00
Dennis Guse d07ab3cc22 TrackRecordingManager: do not store altitude changes twice (previous …
…TrackPoint's storage was delayed).

Fixes #969.
2021-10-16 10:23:09 +02:00
Dennis Guse 90493ae498 TrackRecordingManager: do not store sensor distance twice (previous TrackPoint's storage was delayed).
Fixes #969.
2021-10-16 10:23:09 +02:00
Dennis Guse f6b994572a TrackRecordingManager: cache last stored TrackPoint. 2021-10-16 10:23:09 +02:00
Dennis Guse 24826ff442 TrackRecordingManager: cleanup. 2021-10-16 10:23:09 +02:00
Dennis Guse d97e5ab1ef TrackRecordingManager: only keep lastTrackPoint if was not stored. 2021-10-16 10:23:09 +02:00
Dennis Guse 52a110d998 Bugfix: reset on SensorDataSet should only take place if the TrackPoint was stored
Fixes #969.
2021-10-16 10:23:09 +02:00
Román Martínez 12e18b0bcd Bugfix: activity type preference has to update the icon when user change the activity type. Fixes #985. 2021-10-15 22:16:17 +02:00
Román Martínez 66dac4b0ca Reorganize settings into multiple pages. The main page has icons, titles and summaries. Fixes #960. 2021-10-15 22:04:54 +02:00
Dennis Guse 1bb8cbb82a Running sensor Stryd works.
Fixes #962.
2021-10-15 07:57:29 +02:00
Dennis Guse 2f5b7b63e4 Opening from notification: pressing back goes to TrackListActivity.
Fixes #928.
2021-10-14 18:33:44 +02:00
Ristello 58ca78b976 Removed interface AppConfig, since it is technically not needed. 2021-10-13 22:02:38 +02:00
Ristello 8079a25a42 Use an in-memory database when running instrumented tests.
- added new TestRunner and new TestApplication
- The TestRunner loads TestApplication as the apps Application instance when running the tests
- added new interface AppConfig to retrieve the database name
- changed Startup (Application) so that it implements AppConfig
- changed CustomSQLiteOpenHelper so that it retrieves the database name from the Application instance
- Startup provides the production database name
- TestApplication provides null which yields an in-mememory database
2021-10-13 22:02:38 +02:00
Dennis Guse 0527e4ab14 Cleanup: move AnnouncementUtils to VoiceAnnouncement.
Part of #963
2021-10-12 22:24:51 +02:00
Dennis Guse b6b4c5aa39 Cleanup: move PreferencesUtils into settings package.
Part of #963
2021-10-12 22:24:51 +02:00
Dennis Guse 8c5c9d7947 Cleanup: extract preference functionality from StringUtils to PreferenceHelper.
Part of #963.
2021-10-12 22:24:51 +02:00
Dennis Guse eac3d4b3db Cleanup: SharedPreference as static variable.
Fixes #961.
2021-10-12 22:24:51 +02:00
dependabot[bot] d7bc550b29 Bump gradle from 7.0.2 to 7.0.3
Bumps gradle from 7.0.2 to 7.0.3.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-12 22:16:13 +02:00
Dennis Guse d76d5673a8 Bugfix: running sensor's data should be null if not connected.
Fixes #950.
2021-10-09 14:24:50 +02:00
dependabot[bot] 847be686ea Bump mockito-android from 3.12.4 to 4.0.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.12.4 to 4.0.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.12.4...v4.0.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-08 07:12:11 +02:00
Dennis Guse 1e3445cfa2 Nightly build: app name with suffix.
Part of #952.
2021-10-05 19:23:00 +02:00
Román Martínez d0f7939f1a Added phones with barometric sensor information. 2021-10-01 16:16:16 +02:00
vsevjednom-cz 000b8149f8 Translated using Weblate (Czech)
Currently translated at 99.0% (425 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-09-28 11:06:25 +02:00
Quang Trung 05ffa5a373 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-25 00:06:30 +02:00
Vinicius e92f1107aa Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (429 of 429 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-09-25 00:06:30 +02:00
Quang Trung 2ad1fab670 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-22 22:30:43 +02:00
mondstern 8c04129ef9 Translated using Weblate (Vietnamese)
Currently translated at 98.3% (420 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-09-22 22:30:43 +02:00
Mario 400d9fe948 Translated using Weblate (Spanish)
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-09-22 22:30:42 +02:00
Mario 5d79510750 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (427 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2021-09-22 22:30:42 +02:00
vsevjednom-cz 18bb64580f Translated using Weblate (Slovak)
Currently translated at 71.6% (306 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sk/
2021-09-22 22:30:42 +02:00
vsevjednom-cz 7adebfc0d1 Translated using Weblate (Czech)
Currently translated at 97.8% (418 of 427 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-09-22 22:30:42 +02:00
Román Martínez 2178ff97d3 Release: v3.20.2 2021-09-22 22:30:16 +02:00
Timo Schwarzer f070495fb4 Ask for permission before using Bluetooth 2021-09-22 22:14:53 +02:00
Román Martínez d99fb5ea47 Cadence set to 0 when connected but user is not pedaling. Fixes #931.
Speed set to 0 when connected but last value is outdated. Fixes #930.
Bug: overflow when reset sensors (cadence and speed). Fixes #833.
Moving time stops when is not moving (the last track point is not moving) and restarts when last track point is moving.
2021-09-21 18:07:23 +02:00
Román Martínez d69a726e0e Renaming variables. 2021-09-18 16:17:29 +02:00
Román Martínez e0111c2ffd Gain and loss are integers. 2021-09-18 16:17:29 +02:00
Timo Schwarzer 487bd6c3a0 Set FLAG_IMMUTABLE for PendingIntents
This fixes a crash on Android 12, since it explicitly requires setting either FLAG_MUTABLE or FLAG_IMMUTABLE
2021-09-07 23:18:14 +02:00
Román Martínez 40aab9444a Time interval setting is in minutes. 2021-09-06 22:06:48 +02:00
128 changed files with 2909 additions and 1836 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- uses: actions/setup-java@v2
with:
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- uses: actions/setup-java@v2
with:
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
target: [ default ]
steps:
- name: checkout
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- uses: actions/setup-java@v2
with:
@@ -19,7 +19,7 @@ jobs:
java-version: '11'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.20.0
uses: reactivecircus/android-emulator-runner@v2.21.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
+1 -1
View File
@@ -6,4 +6,4 @@
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
</project>
+25 -4
View File
@@ -1,6 +1,6 @@
# Overview of tested Bluetooth LE sensors
## HRS: Heartrate Service
## 0x180D: Heartrate Service
* Decathlon|Geonaute|Kalenji Dual HRM Belt
* moofit heart rate monitor
@@ -13,7 +13,7 @@
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
* Garmin HRM-Dual (Reference: 010-12883-00)
## CSCP: Cycling Cadence and Speed Service
## 0x1816: Cycling Cadence and Speed Service
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
However, often only one value is provided.
@@ -36,7 +36,7 @@ However, often only one value is provided.
This sensor reports cadence data as speed.
A workaround is in place.
## Cycling Power Measurement
## 0x1818: Power Measurement
We only support _Instantaneous Power_.
@@ -44,6 +44,7 @@ _WARNING_: we only support physically movements with valid GPS coordinates.
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
Also the distance is not computed.
For cycling:
* Assioma Duo Powermeter
* Elite drivo II
* QUARQ Red DZero Powermeter
@@ -51,8 +52,28 @@ Also the distance is not computed.
* Wahoo Kickr v4.0
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
## RSC: running cadence and speed
For running:
* Stryd
## 0x1814: running cadence and speed
We do not support type of movement (i.e., walking vs running).
* Polar Stride
* Stryd (incl. power)
## Tested Barometric Sensor Smartphones
There are smartphones that have a barometric sensor that OpenTracks uses to compute altitude gain and loss.
In our tests there are smartphones whose barometric sensor works well and others don't. Here is a list of the smartphones tested:
|Brand|Model|Android Version|Tests|
|-----|-----|---------------|-----------|
|Google|Pixel|LineageOS 17.1|Ok|
|Google|Pixel 2 XL|LineageOS 17.1|Ok on the bike handlebars but it fails when I carry it in a pocket or backpack|
|Google|Pixel 3|Android 11|Failure|
|Google|Pixel 3a|Android 11|Ok|
|Google|Pixel 3a|Lineage 18.1|Ok|
|Google|Pixel 4a|Android 11|Ok|
|Sony|Xperia XZ1 Compact|LineageOS 16.1|Failure|
+5 -5
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.android.tools.build:gradle:7.0.3'
}
}
@@ -50,8 +50,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4441
versionName "v3.20.1"
versionCode 4496
versionName "v3.21.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -59,7 +59,7 @@ android {
minSdkVersion 21
targetSdkVersion 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
signingConfigs {
@@ -114,7 +114,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.4'
androidTestImplementation 'org.mockito:mockito-android:4.0.0'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
}
@@ -0,0 +1,11 @@
v3.20.2: OpenTracks
Changes:
- Elevation gain and loss are integers instead of floats
- Ask for permission before using Bluetooth by a Toast message
Bugfix:
- Set FLAG_IMMUTABLE for PendingIntents to avoid crash on Android 12
- Cadence sensor returns 0 instead of None when user is not pedaling
- Speed sensor returns 0 instead of the last speed when it's stopped
- With sensors, moving time stops when is not moving
@@ -0,0 +1,19 @@
v3.21.0: OpenTracks
Changes:
- While recording: remove idle/non-idle creation of TrackPoints
- Settings are now in separate folders
- Opening OpenTracks from notification (while recording) and pressing back opens track list
- GPX compatibilty: support <speed> for import
Bugfixes:
- Altitude and distance (from BlE sensor): only reset counters if data was actually stored
- Import without speed, requires computing speed
- BLE running sensor not connected, should not be reported as present
- BLE running sensor: show data while recording
- Activity type icon was not updated while changing activity
Developer:
- SharedPrefences via static reference
- Tests are executed against in-memory database
- TrackRecordingManager keeps last TrackPoint in memory
@@ -1,6 +1,6 @@
OpenTracks estas aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
Eblaĵoj:
Ebloj:
• registri vian sportan kaj alian aktivecon eksterdome
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
• registri ŝanĝon de altitudo uzante barometron
@@ -15,7 +15,6 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -27,7 +26,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.*;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -59,8 +58,7 @@ public class EspressoCustomLayoutTest {
trackControllerRecordButton.perform(click());
// Get custom layout preferences and check all data fields are showed.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
@@ -9,7 +8,7 @@ import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class PreferenceMetricUnitRule implements TestRule {
private final Context context;
@@ -25,14 +24,13 @@ public class PreferenceMetricUnitRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits();
try {
PreferencesUtils.setMetricUnits(sharedPreferences, context, metricUnits);
PreferencesUtils.setMetricUnits(metricUnits);
base.evaluate();
} finally {
PreferencesUtils.setMetricUnits(sharedPreferences, context, previousMetricUnits);
PreferencesUtils.setMetricUnits(previousMetricUnits);
}
}
};
@@ -0,0 +1,9 @@
package de.dennisguse.opentracks;
public class TestApplication extends Startup {
@Override
public String getDatabaseName() {
// null will make SQLiteOpenHelper create an in-memory database
return null;
}
}
@@ -0,0 +1,14 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.runner.AndroidJUnitRunner;
public class TestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, TestApplication.class.getName(), context);
}
}
@@ -78,7 +78,7 @@ public class ChartPointTest {
// given
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setHeartRate_bpm(100f);
trackPoint.setCyclingCadence_rpm(101f);
trackPoint.setCadence_rpm(101f);
trackPoint.setPower(102f);
TrackStatistics statistics = new TrackStatistics();
@@ -120,7 +120,7 @@ public class TestDataUtil {
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate_bpm(100f + i);
trackPoint.setCyclingCadence_rpm(300f + i);
trackPoint.setCadence_rpm(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
@@ -17,7 +17,7 @@ public class TestSensorDataUtil {
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
tp.setHeartRate_bpm(hr);
tp.setCyclingCadence_rpm(cadence);
tp.setCadence_rpm(cadence);
tp.setPower(power);
tp.setHorizontalAccuracy(Distance.of(1f));
tp.setAltitude(1f);
@@ -835,7 +835,7 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
trackPoint.setHeartRate_bpm(1F);
trackPoint.setCyclingCadence_rpm(2F);
trackPoint.setCadence_rpm(2F);
trackPoint.setPower(3F);
// when
@@ -845,7 +845,7 @@ public class CustomContentProviderUtilsTest {
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
assertEquals(trackPoint.getCadence_rpm(), trackPoints.get(10).getCadence_rpm(), 0.01);
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
}
@@ -934,7 +934,7 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
trackPoint.setPower(null);
trackPoint.setCyclingCadence_rpm(null);
trackPoint.setCadence_rpm(null);
trackPoint.setHeartRate_bpm(null);
trackPointList.add(trackPoint);
Track.Id trackId = new Track.Id(System.currentTimeMillis());
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.content.sensor;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -11,6 +12,7 @@ import de.dennisguse.opentracks.util.UintUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@@ -81,7 +83,9 @@ public class SensorDataCyclingTest {
assertEquals(60, current.getValue(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_cadence_rollOverCount() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
@@ -94,6 +98,19 @@ public class SensorDataCyclingTest {
assertEquals(60, current.getValue(), 0.01);
}
@Test
public void compute_cadence_overflow() {
// given
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous);
// then
assertNull(current.getValue());
}
@Test
public void compute_speed() {
// given
@@ -108,7 +125,9 @@ public class SensorDataCyclingTest {
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
}
@Ignore("Disabled from #953")
@Test
@Deprecated
public void compute_speed_rollOverCount() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
@@ -122,6 +141,19 @@ public class SensorDataCyclingTest {
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
}
@Test
public void compute_speed_overflow() {
// given
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
// when
current.compute(previous, Distance.ofMM(2000));
// then
assertNull(current.getValue());
}
@Test
public void equals_speed_with_no_data() {
// given
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@@ -23,6 +24,7 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -67,7 +69,7 @@ public class GPXImportTest {
importTrackId = importer.importFile(inputStream).get(0);
// then
// 1. track
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("the category", importedTrack.getCategory());
@@ -75,6 +77,8 @@ public class GPXImportTest {
assertEquals("2021-01-07 22:51", importedTrack.getName());
assertEquals("UNKNOWN", importedTrack.getIcon());
//TODO Check trackstatistics
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(6, importedTrackPoints.size());
@@ -106,4 +110,102 @@ public class GPXImportTest {
.setAltitude(10)
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_without_speed() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_without_speed);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(1.44, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(53), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(0.7224021553993225))
), importedTrackPoints);
}
@LargeTest
@Test
public void gpx_speed_no_namespace() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_with_speed_no_namespace);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then
// 2. track
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNotNull(importedTrack);
assertEquals("", importedTrack.getCategory());
assertEquals("", importedTrack.getDescription());
assertEquals("20210907_213924.gpx", importedTrack.getName());
assertEquals("", importedTrack.getIcon());
// 3. trackstatistics
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
// 4. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
assertEquals(3, importedTrackPoints.size());
TrackPointAssert a = new TrackPointAssert();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(5)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
.setLatitude(30.14184657)
.setLongitude(-40.38670089)
.setAltitude(-5)
.setSpeed(Speed.of(0.7976524233818054))
.setSpeed(Speed.of(4)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
.setLatitude(30.14185982)
.setLongitude(-40.3863038)
.setAltitude(-5)
.setSpeed(Speed.of(3))
), importedTrackPoints);
}
}
@@ -73,9 +73,9 @@ public class TrackPointAssert {
Assert.assertEquals(expected.getPower(), actual.getPower(), delta);
}
Assert.assertEquals(expected.hasCyclingCadence(), actual.hasCyclingCadence());
if (expected.hasCyclingCadence()) {
Assert.assertEquals(expected.getCyclingCadence_rpm(), actual.getCyclingCadence_rpm(), delta);
Assert.assertEquals(expected.hasCadence(), actual.hasCadence());
if (expected.hasCadence()) {
Assert.assertEquals(expected.getCadence_rpm(), actual.getCadence_rpm(), delta);
}
}
@@ -83,14 +83,14 @@ public class TrackPointAssert {
try {
Assert.assertEquals(expected.size(), actual.size());
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected + " actual: " + actual);
throw new AssertionError("Expected: " + expected + "\n actual: " + actual);
}
for (int i = 0; i < expected.size(); i++) {
try {
assertEquals(expected.get(i), actual.get(i));
} catch (AssertionError e) {
throw new AssertionError("Expected: " + expected.get(i) + " actual: " + actual.get(i), e);
throw new AssertionError("Expected: " + expected.get(i) + "\n actual: " + actual.get(i), e);
}
}
Assert.assertEquals(expected.size(), actual.size());
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.Looper;
@@ -66,15 +65,12 @@ import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Tests for the track recording service.
*
* @author Bartlomiej Niechwiej
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTest {
@@ -97,7 +93,6 @@ public class TrackRecordingServiceTest {
}
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
@@ -124,7 +119,7 @@ public class TrackRecordingServiceTest {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
@@ -1,8 +1,9 @@
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertFalse;
import android.content.ContentProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import androidx.annotation.NonNull;
@@ -32,19 +33,16 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertFalse;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.PressureSensorUtils;
/**
* Tests insert location.
* <p>
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
//TODO Implement as mock test; no need to store data in database
@@ -91,8 +89,7 @@ public class TrackRecordingServiceTestLocation {
tearDown();
// Let's use default values.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
.getService();
@@ -255,13 +252,6 @@ public class TrackRecordingServiceTestLocation {
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Why is this added? Systems is idle and not moving at all.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
@@ -308,20 +298,6 @@ public class TrackRecordingServiceTestLocation {
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(5))
.setSpeed(Speed.of(0))
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
@@ -430,6 +406,88 @@ public class TrackRecordingServiceTestLocation {
), trackPoints);
}
@Deprecated // Will be superseded when fixing #500
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withSensorDistance() {
BluetoothRemoteSensorManager remoteSensorManager = new BluetoothRemoteSensorManager(context) {
@Override
public boolean isEnabled() {
return true;
}
};
AltitudeSumManager altitudeSumManager = new AltitudeSumManager();
// given
Track.Id trackId = service.startNewTrack();
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
altitudeSumManager.stop(service);
altitudeSumManager.setConnected(true);
// when
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0)));
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 15);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(13)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 15);
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14)));
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 15);
altitudeSumManager.addAltitudeGain_m(6f);
altitudeSumManager.addAltitudeLoss_m(6f);
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16)));
service.endCurrentTrack();
// then
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
TrackPointAssert a = new TrackPointAssert()
.ignoreTime();
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(2))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(10)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(4))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
.setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5))
.setAltitudeGain(6f)
.setAltitudeLoss(6f)
.setSensorDistance(Distance.of(2))
), trackPoints);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_segment() {
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,9 +43,6 @@ import static org.junit.Assert.assertTrue;
* Tests for the track recording service, which require a {@link Looper}.
*
* @author Bartlomiej Niechwiej
* <p>
* ATTENTION: This tests deletes all stored tracks in the database.
* So, if it is executed on a real device, data might be lost.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestLooper {
@@ -54,7 +51,6 @@ public class TrackRecordingServiceTestLooper {
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
private ContentProviderUtils contentProviderUtils;
@@ -79,7 +75,7 @@ public class TrackRecordingServiceTestLooper {
contentProviderUtils = new ContentProviderUtils(context);
// Let's use default values.
sharedPreferences.edit().clear().apply();
PreferencesUtils.clear();
// Ensure that the database is empty before every test
contentProviderUtils.deleteAllTracks(context);
@@ -146,14 +142,14 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_default));
fullRecordingSession();
}
@MediumTest
@Test
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
fullRecordingSession();
}
@@ -215,7 +211,7 @@ public class TrackRecordingServiceTestLooper {
.setSpeed(Speed.of(10))
.setBearing(3.0f);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
service.getTrackPointCreator().onNewTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
@@ -5,7 +5,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Looper;
@@ -24,13 +23,12 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class LocationHandlerTest {
private final Context context = ApplicationProvider.getApplicationContext();
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
@Mock
private TrackPointCreator trackPointCreator;
@@ -47,15 +45,12 @@ public class LocationHandlerTest {
@Before
public void setUp() {
// Let's use default values.
sharedPreferences.edit().clear().commit();
PreferencesUtils.clear();
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
//TODO REMOVE
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
// locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
locationHandler.onStart(context, sharedPreferences);
locationHandler.onStart(context);
}
/**
@@ -92,7 +87,7 @@ public class LocationHandlerTest {
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.util.Pair;
@@ -6,6 +6,7 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -18,7 +19,9 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.services.tasks.AnnouncementUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import static org.junit.Assert.assertEquals;
@@ -68,7 +71,7 @@ public class AnnouncementUtilsTest {
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
// then
assertEquals(
Assert.assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -13,6 +14,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -27,14 +29,14 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_ok() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -43,13 +45,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_invalid() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -58,13 +60,13 @@ public class PreferencesUtilsTest {
@Test
public void ExportTrackFileFormat_noValue() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
@@ -73,13 +75,13 @@ public class PreferencesUtilsTest {
@Test
public void testGetCustomLayout_default() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertTrue(layout.getFields().size() > 0);
@@ -90,7 +92,7 @@ public class PreferencesUtilsTest {
@Test
public void testGetCustomLayout_1() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
@@ -102,7 +104,7 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
@@ -128,7 +130,7 @@ public class PreferencesUtilsTest {
@Test
public void testGetCustomLayout_coordinatesIsWide() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
@@ -140,7 +142,7 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
@@ -167,7 +169,7 @@ public class PreferencesUtilsTest {
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
@@ -175,7 +177,7 @@ public class PreferencesUtilsTest {
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
// when
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
PreferencesUtils.setCustomLayout(layoutSrc);
// then
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
@@ -187,7 +189,7 @@ public class PreferencesUtilsTest {
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
Layout layoutDst = PreferencesUtils.getCustomLayout();
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,6 +30,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import static org.junit.Assert.assertEquals;
@@ -47,14 +47,12 @@ public class TrackNameUtilsTest {
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
/**
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
*/
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -63,7 +61,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
@@ -73,7 +71,7 @@ public class TrackNameUtilsTest {
*/
@Test
public void testTrackName_number() {
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
<speed>5</speed>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
<speed>4</speed>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
<speed>3</speed>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -0,0 +1,23 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<name>20210907_213924.gpx</name>
<trkseg>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:10:19Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14184657" lon="-40.38670089">
<ele>-5</ele>
<time>2021-09-07T22:11:07Z</time>
<hdop>4</hdop>
</trkpt>
<trkpt lat="30.14185982" lon="-40.3863038">
<ele>-5</ele>
<time>2021-09-07T22:12:00Z</time>
<hdop>4</hdop>
</trkpt>
</trkseg>
</trk>
</gpx>
+10 -2
View File
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto">
@@ -52,7 +53,10 @@ limitations under the License.
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
@@ -304,7 +308,11 @@ limitations under the License.
<activity android:name=".TrackRecordedActivity" />
<activity android:name=".TrackRecordingActivity" />
<activity android:name=".TrackRecordingActivity" android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
</activity>
<activity android:name=".TrackEditActivity" />
@@ -1,14 +1,12 @@
package de.dennisguse.opentracks;
import android.app.Application;
import android.content.SharedPreferences;
import android.os.StrictMode;
import android.util.Log;
import java.util.Locale;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Code that is executed when the application starts.
@@ -27,13 +25,14 @@ public class Startup extends Application {
// Include version information into stack traces.
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
PreferencesUtils.initPreferences(this, getResources());
// Set default values of preferences on first start.
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
PreferencesUtils.setMetricUnits(sharedPreferences, this, !Locale.US.equals(Locale.getDefault()));
PreferencesUtils.resetPreferences(this, false);
if (PreferencesUtils.getString( R.string.stats_units_key, "").equals("")) {
PreferencesUtils.setMetricUnits(!Locale.US.equals(Locale.getDefault()));
}
PreferencesUtils.applyNightMode();
ActivityUtils.applyNightMode(sharedPreferences, this);
// In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) {
@@ -41,4 +40,13 @@ public class Startup extends Application {
StrictMode.enableDefaults();
}
}
/**
* Returns the name of the database used by SQLiteOpenHelper.
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
* @return SQLite database name.
*/
public String getDatabaseName() {
return "database.db";
}
}
@@ -21,7 +21,6 @@ import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.database.Cursor;
@@ -64,7 +63,7 @@ import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -78,7 +77,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private static final String TAG = TrackListActivity.class.getSimpleName();
// The following are setFrequency in onCreate
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private ResourceCursorAdapter resourceCursorAdapter;
@@ -110,8 +108,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
};
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
metricUnits = PreferencesUtils.isMetricUnits();
}
if (key != null) {
runOnUiThread(() -> {
@@ -161,8 +159,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
@@ -231,8 +227,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this);
}
@@ -249,7 +244,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(this);
}
@@ -47,7 +47,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* An activity to show the track detail, record a new track or resumes an existing one.
@@ -68,7 +68,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
// The following are setFrequency in onCreate.
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private TrackRecordedBinding viewBinding;
@@ -96,8 +95,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackRecordedActivity.this, R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordedActivity.this));
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
}
};
@@ -107,7 +106,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
handleIntent(getIntent());
@@ -130,8 +128,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub.start();
}
@@ -155,7 +152,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
super.onStop();
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -31,7 +31,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
@@ -53,7 +53,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// The following are setFrequency in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackDataHub trackDataHub;
@@ -96,17 +95,17 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_show_on_lockscreen_while_recording_key, key)) {
setLockscreenPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy();
}
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordingActivity.this));
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
}
if (key == null) return;
@@ -121,7 +120,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackId = null;
if (savedInstanceState != null) {
//Activity was recreated.
@@ -156,7 +154,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen);
@@ -168,7 +166,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn();
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -178,7 +176,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void setFullscreenPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen();
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -191,8 +189,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onStart() {
super.onStart();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this);
trackDataHub.start();
@@ -230,7 +227,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
protected void onStop() {
super.onStop();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(this);
trackDataHub.stop();
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,7 +10,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
@@ -58,7 +57,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
viewHolder = (ViewHolder) convertView.getTag();
}
if (TrackIconUtils.isSpeedIcon(context, aggregatedStatistic.getCategory())) {
if (TrackIconUtils.isSpeedIcon(context.getResources(), aggregatedStatistic.getCategory())) {
viewHolder.setSpeed(aggregatedStatistic);
} else {
viewHolder.setPace(aggregatedStatistic);
@@ -139,9 +138,8 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String category = aggregatedStatistic.getCategory();
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
reportSpeed = PreferencesUtils.isReportSpeed(category);
metricsUnits = PreferencesUtils.isMetricUnits();
sportIcon.setImageResource(getIcon(aggregatedStatistic));
typeLabel.setText(category);
@@ -59,8 +59,8 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
viewHolder.gain.setText(StringUtils.formatAltitude(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatAltitude(context, interval.getLoss_m(), metricUnits));
viewHolder.gain.setText(StringUtils.formatAltitudeChange(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatAltitudeChange(context, interval.getLoss_m(), metricUnits));
}
@@ -41,8 +41,8 @@ public class ChartPoint {
if (trackPoint.hasHeartRate()) {
heartRate = (double) trackPoint.getHeartRate_bpm();
}
if (trackPoint.hasCyclingCadence()) {
cadence = (double) trackPoint.getCyclingCadence_rpm();
if (trackPoint.hasCadence()) {
cadence = (double) trackPoint.getCadence_rpm();
}
if (trackPoint.hasPower()) {
power = (double) trackPoint.getPower();
@@ -1,6 +1,7 @@
package de.dennisguse.opentracks.content.data;
import android.content.Context;
import android.content.res.Resources;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.CsvConstants;
@@ -57,43 +58,43 @@ public class DataField {
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
}
public static String getTitleByKey(Context context, String key) {
if (key.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
return context.getString(R.string.stats_total_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
return context.getString(R.string.stats_moving_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
return context.getString(R.string.stats_distance);
} else if (key.equals(context.getString(R.string.stats_custom_layout_speed_key))) {
return context.getString(R.string.stats_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
return context.getString(R.string.stats_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return context.getString(R.string.stats_average_moving_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
return context.getString(R.string.stats_average_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
return context.getString(R.string.stats_max_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return context.getString(R.string.stats_average_moving_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
return context.getString(R.string.stats_average_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return context.getString(R.string.stats_fastest_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
return context.getString(R.string.stats_altitude);
} else if (key.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
return context.getString(R.string.stats_gain);
} else if (key.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
return context.getString(R.string.stats_loss);
} else if (key.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
return context.getString(R.string.stats_coordinates);
} else if (key.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
return context.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
return context.getString(R.string.stats_sensors_cadence);
} else if (key.equals(context.getString(R.string.stats_custom_layout_power_key))) {
return context.getString(R.string.stats_sensors_power);
public static String getTitleByKey(Resources resources, String key) {
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
return resources.getString(R.string.stats_total_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
return resources.getString(R.string.stats_moving_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
return resources.getString(R.string.stats_distance);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
return resources.getString(R.string.stats_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
return resources.getString(R.string.stats_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return resources.getString(R.string.stats_average_moving_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
return resources.getString(R.string.stats_average_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
return resources.getString(R.string.stats_max_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return resources.getString(R.string.stats_average_moving_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
return resources.getString(R.string.stats_average_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return resources.getString(R.string.stats_fastest_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
return resources.getString(R.string.stats_altitude);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
return resources.getString(R.string.stats_gain);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
return resources.getString(R.string.stats_loss);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
return resources.getString(R.string.stats_coordinates);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
return resources.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
return resources.getString(R.string.stats_sensors_cadence);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
return resources.getString(R.string.stats_sensors_power);
} else {
throw new RuntimeException("It doesn't exists a field with key: " + key);
}
@@ -91,7 +91,7 @@ public class TrackPoint {
private Type type;
private Float heartRate_bpm = null;
private Float cyclingCadence_rpm = null;
private Float cadence_rpm = null;
private Float power = null;
private Float altitudeGain_m = null;
private Float altitudeLoss_m = null;
@@ -349,8 +349,21 @@ public class TrackPoint {
return this;
}
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
if (hasSensorDistance() && lastTrackPoint.hasSensorDistance()) {
sensorDistance = sensorDistance.minus(lastTrackPoint.getSensorDistance());
}
if (hasAltitudeGain() && lastTrackPoint.hasAltitudeGain()) {
altitudeGain_m -= lastTrackPoint.altitudeGain_m;
}
if (hasAltitudeLoss() && lastTrackPoint.hasAltitudeLoss()) {
altitudeLoss_m -= lastTrackPoint.altitudeLoss_m;
}
return this;
}
public boolean hasSensorData() {
return hasHeartRate() || hasCyclingCadence() || hasPower();
return hasHeartRate() || hasCadence() || hasPower();
}
public boolean hasHeartRate() {
@@ -366,16 +379,16 @@ public class TrackPoint {
return this;
}
public boolean hasCyclingCadence() {
return cyclingCadence_rpm != null;
public boolean hasCadence() {
return cadence_rpm != null;
}
public float getCyclingCadence_rpm() {
return cyclingCadence_rpm;
public float getCadence_rpm() {
return cadence_rpm;
}
public TrackPoint setCyclingCadence_rpm(Float cyclingCadence_rpm) {
this.cyclingCadence_rpm = cyclingCadence_rpm;
public TrackPoint setCadence_rpm(Float cadence_rpm) {
this.cadence_rpm = cadence_rpm;
return this;
}
@@ -396,15 +409,17 @@ public class TrackPoint {
@Override
public String toString() {
String result = "time=" + getTime() + " (type=" + getType() + ")";
if (!hasLocation()) {
return result;
if (hasLocation()) {
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
}
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
if (!hasHorizontalAccuracy()) {
return result;
if (hasHorizontalAccuracy()) {
result += " acc=" + getHorizontalAccuracy();
}
if (hasSensorDistance()) {
result += " distance=" + getSensorDistance();
}
return result + " acc=" + getHorizontalAccuracy();
return result;
}
public static class Id {
@@ -558,7 +558,7 @@ public class ContentProviderUtils {
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
}
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
trackPoint.setCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
@@ -719,8 +719,8 @@ public class ContentProviderUtils {
if (trackPoint.hasHeartRate()) {
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
}
if (trackPoint.hasCyclingCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
if (trackPoint.hasCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence_rpm());
}
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.UUID;
import de.dennisguse.opentracks.Startup;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
@@ -27,11 +28,8 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 31;
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
public CustomSQLiteOpenHelper(Context context) {
this(context, DATABASE_NAME);
this(context, ((Startup) context.getApplicationContext()).getDatabaseName());
}
@VisibleForTesting
@@ -50,8 +50,17 @@ public abstract class SensorData<T> {
return value != null;
}
@NonNull
protected abstract T getNoneValue();
public T getValue() {
return value;
if (!hasValue()) {
return null;
}
if (isRecent()) {
return value;
}
return getNoneValue();
}
/**
@@ -63,7 +72,7 @@ public abstract class SensorData<T> {
/**
* Is the data recent considering the current time.
*/
public boolean isRecent() {
private boolean isRecent() {
return Instant.now()
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
}
@@ -59,17 +59,30 @@ public final class SensorDataCycling {
return crankRevolutionsTime;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
public void compute(Cadence previous) {
if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
if (timeDiff_ms <= 0) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
value = null;
} else {
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms;
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
return;
}
// TODO We have to treat with overflow according to the documentation: read https://github.com/OpenTracksApp/OpenTracks/pull/953#discussion_r711625268
if (crankRevolutionsCount < previous.crankRevolutionsCount) {
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
return;
}
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms;
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
}
}
@@ -121,6 +134,16 @@ public final class SensorDataCycling {
return wheelRevolutionsTime;
}
@NonNull
@Override
protected Data getNoneValue() {
if (value != null) {
return new Data(value.distance, value.distanceOverall, Speed.zero());
} else {
return new Data(Distance.of(0), Distance.of(0), Speed.zero());
}
}
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
@@ -131,6 +154,10 @@ public final class SensorDataCycling {
return;
}
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
return;
}
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
@@ -217,6 +244,12 @@ public final class SensorDataCycling {
public DistanceSpeed getDistanceSpeed() {
return this.value != null ? this.value.second : null;
}
@NonNull
@Override
protected Pair<Cadence, DistanceSpeed> getNoneValue() {
return new Pair<>(null, null);
}
}
}
@@ -18,4 +18,10 @@ public class SensorDataCyclingPower extends SensorData<Float> {
public String toString() {
return super.toString() + " power=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -18,4 +18,10 @@ public class SensorDataHeartRate extends SensorData<Float> {
public String toString() {
return super.toString() + " heart=" + value;
}
@NonNull
@Override
protected Float getNoneValue() {
return 0f;
}
}
@@ -43,7 +43,6 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
return cadence;
}
@VisibleForTesting
public Speed getSpeed() {
return speed;
}
@@ -53,16 +52,28 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
return totalDistance;
}
public void compute(SensorDataRunning previous) {
Distance overallDistance = null;
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().getDistance() != null) {
overallDistance = overallDistance.plus(previous.getValue().getDistance());
}
@NonNull
@Override
protected Data getNoneValue() {
if (value != null) {
return new Data(Speed.zero(), 0f, value.distance);
} else {
return new Data(Speed.zero(), 0f, Distance.of(0));
}
}
value = new Data(speed, cadence, overallDistance);
public void compute(SensorDataRunning previous) {
if (speed != null && hasTotalDistance()) {
Distance overallDistance = null;
if (previous != null && previous.hasTotalDistance()) {
overallDistance = this.totalDistance.minus(previous.totalDistance);
if (previous.hasValue() && previous.getValue().getDistance() != null) {
overallDistance = overallDistance.plus(previous.getValue().getDistance());
}
}
value = new Data(speed, cadence, overallDistance);
}
}
@Override
@@ -1,7 +1,10 @@
package de.dennisguse.opentracks.content.sensor;
import android.util.Pair;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
public final class SensorDataSet {
@@ -27,8 +30,36 @@ public final class SensorDataSet {
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
}
public SensorDataHeartRate getHeartRate() {
return heartRate;
public Pair<Float, String> getHeartRate() {
if (heartRate != null) {
return new Pair<>(heartRate.getValue(), heartRate.getSensorNameOrAddress());
}
return null;
}
public Pair<Float, String> getCadence() {
if (cyclingCadence != null) {
return new Pair<>(cyclingCadence.getValue(), cyclingCadence.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null) {
return new Pair<>(runningDistanceSpeedCadence.getCadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public Pair<Speed, String> getSpeed() {
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue() && cyclingDistanceSpeed.getValue().getSpeed() != null) {
return new Pair<>(cyclingDistanceSpeed.getValue().getSpeed(), cyclingDistanceSpeed.getSensorNameOrAddress());
}
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().getSpeed() != null) {
return new Pair<>(runningDistanceSpeedCadence.getSpeed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
}
return null;
}
public SensorDataCycling.Cadence getCyclingCadence() {
@@ -64,17 +95,20 @@ public final class SensorDataSet {
}
public void fillTrackPoint(TrackPoint trackPoint) {
if (heartRate != null) {
trackPoint.setHeartRate_bpm(heartRate.getValue());
if (getHeartRate() != null) {
trackPoint.setHeartRate_bpm(getHeartRate().first);
}
if (cyclingCadence != null && cyclingCadence.hasValue()) {
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
if (getCadence() != null) {
trackPoint.setCadence_rpm(getCadence().first);
}
if (getSpeed() != null) {
trackPoint.setSpeed(getSpeed().first);
}
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().getDistanceOverall());
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().getSpeed());
}
if (cyclingPower != null && cyclingPower.hasValue()) {
@@ -83,8 +117,6 @@ public final class SensorDataSet {
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
trackPoint.setSpeed(runningDistanceSpeedCadence.getValue().getSpeed());
trackPoint.setCyclingCadence_rpm(runningDistanceSpeedCadence.getValue().getCadence());
}
}
@@ -93,18 +125,17 @@ public final class SensorDataSet {
if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
}
@NonNull
@Override
public String toString() {
return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : "")
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
return (heartRate != null ? "" + heartRate : "")
+ (cyclingCadence != null ? " " + cyclingCadence : "")
+ (cyclingDistanceSpeed != null ? " " + cyclingDistanceSpeed : "")
+ (cyclingPower != null ? " " + cyclingPower : "")
+ (runningDistanceSpeedCadence != null ? " " + runningDistanceSpeedCadence : "");
}
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* A fragment to display track chart to the user.
@@ -64,8 +64,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartFragment;
}
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
// Stats gathered from the received data
@@ -80,8 +78,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits();
if (metricUnits != viewBinding.chartView.getMetricUnits()) {
viewBinding.chartView.setMetricUnits(metricUnits);
runOnUiThread(() -> {
@@ -91,8 +89,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
});
}
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
viewBinding.chartView.setReportSpeed(reportSpeed);
viewBinding.chartView.applyReportSpeed();
@@ -141,9 +139,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onResume() {
super.onResume();
resumeTrackDataHub();
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
checkChartSettings();
getActivity().runOnUiThread(updateChart);
@@ -153,7 +150,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
public void onPause() {
super.onPause();
pauseTrackDataHub();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -162,12 +159,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
viewBinding = null;
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
@Override
public void onTrackUpdated(Track track) {
if (isResumed()) {
@@ -177,7 +168,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
category = track.getCategory();
boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
viewBinding.chartView.setReportSpeed(reportSpeed);
viewBinding.chartView.applyReportSpeed();
@@ -23,7 +23,7 @@ import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
@@ -46,15 +46,13 @@ public class IntervalsFragment extends Fragment {
private IntervalStatisticsAdapter adapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
private SharedPreferences sharedPreferences;
private boolean isReportSpeed;
private IntervalListViewBinding viewBinding;
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
updateIntervals(PreferencesUtils.isMetricUnits(sharedPreferences, getContext()), selectedInterval);
if (PreferencesUtils.isKey(R.string.stats_units_key, key) || PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
updateIntervals(PreferencesUtils.isMetricUnits(), selectedInterval);
if (spinnerAdapter != null) {
spinnerAdapter.notifyDataSetChanged();
}
@@ -93,8 +91,6 @@ public class IntervalsFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
adapter = new IntervalStatisticsAdapter(getContext(), stackModeListView, metricUnits, isReportSpeed);
viewBinding.intervalList.setLayoutManager(new LinearLayoutManager(getContext()));
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
@@ -136,13 +132,12 @@ public class IntervalsFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
Track track = contentProviderUtils.getTrack(trackId);
if (track != null) {
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
}
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
@@ -152,7 +147,9 @@ public class IntervalsFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
if (viewModel != null) {
viewModel.onPause();
}
@@ -168,8 +165,6 @@ public class IntervalsFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
adapter = null;
viewModel = null;
}
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.StatisticData;
@@ -79,21 +79,20 @@ public class StatisticsRecordedFragment extends Fragment {
private StatisticsRecordedBinding viewBinding;
private SharedPreferences sharedPreferences;
private boolean preferenceMetricUnits;
private boolean preferenceReportSpeed;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
if (PreferencesUtils.isKey(R.string.stats_rate_key, key) && track != null) {
updateUInecessary = true;
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
preferenceReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
}
if (key != null && updateUInecessary && isResumed()) {
@@ -112,8 +111,6 @@ public class StatisticsRecordedFragment extends Fragment {
trackId = getArguments().getParcelable(TRACK_ID_KEY);
contentProviderUtils = new ContentProviderUtils(getContext());
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
}
@@ -132,24 +129,22 @@ public class StatisticsRecordedFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
loadStatistics();
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDestroyView() {
super.onDestroyView();
viewBinding = null;
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
public void loadStatistics() {
@@ -168,7 +163,7 @@ public class StatisticsRecordedFragment extends Fragment {
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
this.track = track;
if (prefsChanged) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
loadTrackDescription(track);
@@ -243,11 +238,11 @@ public class StatisticsRecordedFragment extends Fragment {
Pair<String, String> parts;
parts = StringUtils.getAltitudeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
viewBinding.statsAltitudeGainValue.setText(parts.first);
viewBinding.statsAltitudeGainUnit.setText(parts.second);
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
@@ -55,24 +55,23 @@ public class StatisticsRecordingFragment extends Fragment {
private StatisticsDataModel viewModel;
private LiveData<List<StatisticData>> statisticsLiveData;
private SharedPreferences sharedPreferences;
private boolean preferenceMetricUnits;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_fields_key, key)) {
if (PreferencesUtils.isKey(R.string.stats_custom_layout_fields_key, key)) {
updateUInecessary = true;
layout = PreferencesUtils.getCustomLayout(sharedPreferences, getContext());
layout = PreferencesUtils.getCustomLayout();
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_columns_key, key)) {
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns(sharedPreferences, getContext()));
if (PreferencesUtils.isKey(R.string.stats_custom_layout_columns_key, key)) {
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns());
}
if (key != null && updateUInecessary && isResumed()) {
@@ -97,7 +96,6 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
statisticsAdapter = new StatisticsAdapter(getContext());
@@ -109,7 +107,7 @@ public class StatisticsRecordingFragment extends Fragment {
RecyclerView recyclerView = viewBinding.statsRecyclerView;
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
final int numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, getContext());
final int numColumns = PreferencesUtils.getLayoutColumns();
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
@@ -130,8 +128,7 @@ public class StatisticsRecordingFragment extends Fragment {
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
statisticsLiveData = viewModel.getStatsData();
@@ -143,7 +140,7 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -162,9 +159,12 @@ public class StatisticsRecordingFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
sharedPreferences = null;
viewModel = null;
statisticsLiveData.removeObservers(getActivity());
if (statisticsLiveData != null) {
statisticsLiveData.removeObservers(getActivity());
}
statisticsLiveData = null;
}
private void updateUI() {
@@ -179,7 +179,7 @@ public class StatisticsRecordingFragment extends Fragment {
this.recordingData = recordingData;
if (!oldCategory.equals(newCategory)) {
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
latestTrackPoint = recordingData.getLatestTrackPoint();
@@ -285,7 +285,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
if (trackPoint.hasSpeed()) {
@@ -296,8 +296,8 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
}
if (trackPoint.hasCyclingCadence()) {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
if (trackPoint.hasCadence()) {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>");
}
if (trackPoint.hasPower()) {
@@ -382,7 +382,7 @@ public class KMLTrackExporter implements TrackExporter {
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
cadenceList.add(trackPoint.hasCadence() ? trackPoint.getCadence_rpm() : null);
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
@@ -14,7 +14,7 @@ import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.databinding.TrackSharePictureBinding;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -61,12 +61,12 @@ public class SharePictureTrackExporter implements TrackExporter {
TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context));
viewBinding.sharePictureCategory.setText(track.getCategory());
boolean metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
boolean metricUnits = PreferencesUtils.isMetricUnits();
TrackStatistics trackStatistics = track.getTrackStatistics();
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits));
viewBinding.sharePictureGain.setText(StringUtils.formatAltitude(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
viewBinding.sharePictureGain.setText(StringUtils.formatAltitudeChange(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
// prepare rendering
View view = viewBinding.getRoot();
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
@@ -13,20 +12,16 @@ import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
protected SharedPreferences sharedPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
if (directoryUri == null) {
@@ -46,7 +41,6 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
@@ -91,14 +85,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
return PreferencesUtils.getDefaultExportDirectoryUri(this);
}
@Override
protected Intent createNextActivityIntent(Uri directoryUri) {
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(sharedPreferences, this));
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat());
return intent;
}
}
@@ -112,11 +106,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
case RESULT_OK:
Uri directoryUri = resultData.getData();
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
break;
case RESULT_CANCELED:
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
PreferencesUtils.setDefaultExportDirectoryUri(null);
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
break;
}
@@ -129,8 +123,8 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
super.configureDirectoryChooserIntent(intent);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
}
return null;
}
@@ -70,6 +70,10 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
private static final String ATTRIBUTE_LON = "lon";
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
/**
* Often speed is exported without the proper namespace.
*/
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
@@ -192,6 +196,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
}
break;
case TAG_EXTENSION_SPEED:
case TAG_EXTENSION_SPEED_COMPAT:
if (content != null) {
speed = content.trim();
}
@@ -295,7 +300,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
if (cadence != null) {
try {
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
trackPoint.setCadence_rpm(Float.parseFloat(cadence));
} catch (Exception e) {
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
}
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.io.file.importer;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -22,7 +21,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class ImportService extends JobIntentService {
@@ -54,10 +53,9 @@ public class ImportService extends JobIntentService {
String fileExtension = FileUtils.getExtension(file);
try {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(this);
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, this);
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, this);
boolean preventReimport = PreferencesUtils.getPreventReimportTracks(sharedPreferences, this);
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), recordingDistanceInterval, maxRecordingDistance, preventReimport);
@@ -316,7 +316,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
trackPoint.setHeartRate_bpm(sensorHeartRateList.get(i));
}
if (i < sensorCadenceList.size()) {
trackPoint.setCyclingCadence_rpm(sensorCadenceList.get(i));
trackPoint.setCadence_rpm(sensorCadenceList.get(i));
}
if (i < sensorPowerList.size()) {
trackPoint.setPower(sensorPowerList.get(i));
@@ -144,6 +144,8 @@ public class TrackImporter {
return 0;
});
adjustTrackPoints();
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
track.setTrackStatistics(updater.getTrackStatistics());
@@ -151,7 +153,6 @@ public class TrackImporter {
Track.Id trackId = contentProviderUtils.insertTrack(track);
// Store TrackPoints
adjustTrackPoints();
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
// Store Markers
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services;
import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
@@ -20,7 +19,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackNameUtils;
@@ -37,9 +36,9 @@ class TrackRecordingManager {
private Track.Id trackId;
private TrackStatisticsUpdater trackStatisticsUpdater;
private boolean currentSegmentHasTrackPoint;
private TrackPoint lastTrackPoint;
private TrackPoint lastValidTrackPoint;
private boolean isIdle;
private TrackPoint lastStoredTrackPoint;
TrackRecordingManager(Context context) {
this.context = context;
@@ -59,12 +58,14 @@ class TrackRecordingManager {
//TODO Pass TrackPoint
track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
String category = PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), context); //TODO Re-use sharedpreferences
String category = PreferencesUtils.getDefaultActivity();
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(track);
currentSegmentHasTrackPoint = false;
return trackId;
}
@@ -79,40 +80,38 @@ class TrackRecordingManager {
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
insertTrackPoint(trackId, segmentStartTrackPoint);
}
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null; }
void pause(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
currentSegmentHasTrackPoint = false;
lastTrackPoint = null;
lastStoredTrackPoint = null;
}
void end(TrackPointCreator trackPointCreator) {
if (lastTrackPoint != null) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
}
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
insertTrackPoint(trackId, segmentEnd);
trackId = null;
trackStatisticsUpdater = null;
lastTrackPoint = null;
lastValidTrackPoint = null;
isIdle = false;
lastStoredTrackPoint = null;
currentSegmentHasTrackPoint = false;
}
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator trackPointCreator) {
if (trackPointCreator == null) {
return null;
}
TrackStatisticsUpdater tmpTrackStatisticsUpdater = getTrackStatisticsUpdater();
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPoint);
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
Track track = getTrack(); //Get copy
Track track = contentProviderUtils.getTrack(trackId); //Get copy
if (track == null) {
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
return null;
@@ -132,8 +131,7 @@ class TrackRecordingManager {
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (trackPoint == null) {
if (lastStoredTrackPoint == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
}
@@ -143,130 +141,68 @@ class TrackRecordingManager {
String icon = context.getString(R.string.marker_icon_url);
photoUrl = photoUrl != null ? photoUrl : "";
// Insert marker
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), trackPoint, photoUrl);
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPoint, photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
boolean onNewTrackPoint(TrackPoint trackPoint) {
//Storing trackPoint
// Always insert the first segment location
if (!currentSegmentHasTrackPoint()) {
if (!currentSegmentHasTrackPoint) {
insertTrackPoint(trackId, trackPoint);
lastTrackPoint = trackPoint;
return;
currentSegmentHasTrackPoint = true;
return true;
}
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastStoredTrackPoint);
if (distanceToLastTrackLocation != null) {
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
return true;
}
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
if (trackPoint.hasSensorData() || (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving())) {
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
return true;
}
}
if (!isIdle && !trackPoint.isMoving()) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = true;
lastTrackPoint = trackPoint;
return;
}
if (isIdle && trackPoint.isMoving()) {
insertTrackPointIfNewer(trackId, lastTrackPoint);
insertTrackPoint(trackId, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
}
Log.d(TAG, "Not recording TrackPoint, idle");
lastTrackPoint = trackPoint;
}
Track getTrack() {
return contentProviderUtils.getTrack(trackId);
}
//Functionality that uses this method should happen here.
@Deprecated
TrackStatisticsUpdater getTrackStatisticsUpdater() {
return new TrackStatisticsUpdater(trackStatisticsUpdater);
return false;
}
TrackStatistics getTrackStatistics() {
return trackStatisticsUpdater.getTrackStatistics();
}
/**
* Gets the last valid track point in the current segment.
*
* @param trackId the track id
* @return the location or null
*/
@Deprecated
//Use lastValidTrackPoint
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
if (!currentSegmentHasTrackPoint()) {
return null;
}
return contentProviderUtils.getLastValidTrackPoint(trackId);
}
private boolean currentSegmentHasTrackPoint() {
return lastTrackPoint != null;
}
/**
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
*/
private void insertTrackPointIfNewer(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(trackId);
if (lastValidTrackPoint != null && trackPoint.getTime().equals(lastValidTrackPoint.getTime())) {
// Do not insert if inserted already
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
return;
}
insertTrackPoint(trackId, trackPoint);
}
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
if (lastTrackPoint != null) {
if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) {
// Do not insert if inserted already
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
} else {
insertTrackPointHelper(trackId, lastTrackPoint);
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
trackPoint.minusCumulativeSensorData(lastTrackPoint);
}
lastTrackPoint = null;
}
insertTrackPointHelper(trackId, trackPoint);
}
private void insertTrackPointHelper(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
try {
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
lastStoredTrackPoint = trackPoint;
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -276,12 +212,12 @@ class TrackRecordingManager {
}
}
public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, String key) {
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
public void onSharedPreferenceChanged(String key) {
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
}
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.max_recording_distance_key, key)) {
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
}
}
}
@@ -54,7 +54,7 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.ExportUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
/**
@@ -96,25 +96,23 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
};
private SharedPreferences sharedPreferences;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Context context = TrackRecordingService.this;
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
boolean metricUnits = PreferencesUtils.isMetricUnits();
notificationManager.setMetricUnits(metricUnits);
}
if (PreferencesUtils.isKey(context, R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency(sharedPreferences, context));
if (PreferencesUtils.isKey(R.string.voice_announcement_frequency_key, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency());
}
if (PreferencesUtils.isKey(context, new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance(sharedPreferences, context));
if (PreferencesUtils.isKey(new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance());
}
trackPointCreator.onSharedPreferenceChanged(sharedPreferences, key);
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
trackPointCreator.onSharedPreferenceChanged(key);
trackRecordingManager.onSharedPreferenceChanged(key);
}
};
@@ -148,9 +146,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
notificationManager = new TrackRecordingServiceNotificationManager(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
@@ -173,8 +169,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
// Reverse order from onCreate
showNotification(false); //TODO Why?
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferences = null;
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
try {
voiceAnnouncementManager.shutdown();
@@ -360,14 +355,14 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
@Override
public void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
public boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
if (!isRecording() || isPaused()) {
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
return;
return false;
}
trackRecordingManager.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
return trackRecordingManager.onNewTrackPoint(trackPoint);
}
@Override
@@ -382,12 +377,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingStatus.getTrackId());
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class);
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackRecordingActivity.class)
.addNextIntent(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, pendingIntentFlags);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
@@ -395,9 +394,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
if (!isRecording() && isGpsStarted) {
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
int pendingIntentFlags = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
}
PendingIntent pendingIntent = TaskStackBuilder.create(this)
.addParentStack(TrackListActivity.class)
.addNextIntent(intent)
.getPendingIntent(0, 0);
.getPendingIntent(0, pendingIntentFlags);
notificationManager.updatePendingIntent(pendingIntent);
notificationManager.updateContent(getString(R.string.gps_starting));
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.LocationManager;
import android.os.Handler;
@@ -13,7 +12,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* This class handle GPS status according to received locations` and some thresholds.
@@ -68,10 +67,9 @@ class GpsStatus {
this.client = client;
this.context = context;
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
thresholdHorizontalAccuracy = PreferencesUtils.getRecordingDistanceInterval();
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
Duration minRecordingInterval = PreferencesUtils.getMinRecordingInterval();
signalLostThreshold = SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval);
gpsStatusHandler = new Handler();
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
@@ -17,7 +16,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class LocationHandler implements LocationListener, GpsStatus.GpsStatusListener {
@@ -35,8 +34,8 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
this.trackPointCreator = trackPointCreator;
}
public void onStart(@NonNull Context context, SharedPreferences sharedPreferences) {
onSharedPreferenceChanged(context, sharedPreferences, null);
public void onStart(@NonNull Context context) {
onSharedPreferenceChanged(null);
gpsStatus = new GpsStatus(context, this);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
registerLocationListener();
@@ -62,26 +61,26 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
}
}
public void onSharedPreferenceChanged(@NonNull Context context, @NonNull SharedPreferences sharedPreferences, String key) {
public void onSharedPreferenceChanged(String key) {
boolean registerListener = false;
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
if (PreferencesUtils.isKey(R.string.min_recording_interval_key, key)) {
registerListener = true;
gpsInterval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, context);
gpsInterval = PreferencesUtils.getMinRecordingInterval();
if (gpsStatus != null) {
gpsStatus.onMinRecordingIntervalChanged(gpsInterval);
}
}
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.recording_gps_accuracy_key, key)) {
thresholdHorizontalAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
}
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
registerListener = true;
if (gpsStatus != null) {
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
Distance gpsMinDistance = PreferencesUtils.getRecordingDistanceInterval();
gpsStatus.onRecordingDistanceChanged(gpsMinDistance);
}
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.util.Pair;
@@ -17,7 +16,6 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
@@ -51,8 +49,7 @@ public class TrackPointCreator {
public void start(@NonNull Context context) {
this.context = context;
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
locationHandler.onStart(context, sharedPreferences);
locationHandler.onStart(context);
remoteSensorManager = new BluetoothRemoteSensorManager(context);
remoteSensorManager.start();
@@ -86,14 +83,6 @@ public class TrackPointCreator {
return sensorDataSet;
}
public SensorDataSet fillAndReset(TrackPoint trackPoint) {
SensorDataSet sensorDataSet = fill(trackPoint);
resetSensorData();
return sensorDataSet;
}
public void stop() {
locationHandler.onStop();
@@ -110,19 +99,17 @@ public class TrackPointCreator {
this.context = null;
}
public void onSharedPreferenceChanged(@NonNull SharedPreferences preferences, String key) {
if (context == null) {
Log.w(TAG, "not started yet.");
return;
}
locationHandler.onSharedPreferenceChanged(context, preferences, key);
public void onSharedPreferenceChanged(String key) {
locationHandler.onSharedPreferenceChanged(key);
}
public void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
fillAndReset(trackPoint);
fill(trackPoint);
service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy);
boolean stored = service.newTrackPoint(trackPoint, thresholdHorizontalAccuracy);
if (stored) {
resetSensorData();
}
}
public TrackPoint createSegmentStartManual() {
@@ -131,7 +118,8 @@ public class TrackPointCreator {
public TrackPoint createSegmentEnd() {
TrackPoint segmentEnd = TrackPoint.createSegmentEndWithTime(createNow());
fillAndReset(segmentEnd);
fill(segmentEnd);
resetSensorData();
return segmentEnd;
}
@@ -189,7 +177,10 @@ public class TrackPointCreator {
}
public interface Callback {
void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy);
/**
* @return Was TrackPoint stored (not discarded)?
*/
boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy);
void newGpsStatus(GpsStatusValue gpsStatusValue);
}
@@ -70,6 +70,19 @@ public class AltitudeSumManager implements SensorEventListener {
return isConnected ? altitudeGain_m : null;
}
@VisibleForTesting
public void addAltitudeGain_m(float altitudeGain_m) {
this.altitudeGain_m = this.altitudeGain_m == null ? 0f : this.altitudeGain_m;
this.altitudeGain_m += altitudeGain_m ;
}
@VisibleForTesting
public void addAltitudeLoss_m(Float altitudeLoss_m) {
this.altitudeLoss_m = this.altitudeLoss_m == null ? 0f : this.altitudeLoss_m;
this.altitudeLoss_m += altitudeLoss_m ;
}
public @Nullable
Float getAltitudeLoss_m() {
return isConnected ? altitudeLoss_m : null;
@@ -34,7 +34,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
@@ -61,7 +61,6 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private boolean started = false;
private final SharedPreferences sharedPreferences;
private Distance preferenceWheelCircumference;
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
@@ -77,34 +76,34 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!started) return;
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress();
connect(heartRate, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingCadenceSensorAddress();
connect(cyclingCadence, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingSpeedSensorAddress();
connect(cyclingSpeed, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, key)) {
preferenceWheelCircumference = PreferencesUtils.getWheelCircumference();
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_cycling_power_key, key)) {
String address = PreferencesUtils.getBluetoothCyclingPowerSensorAddress();
connect(cyclingPower, address);
}
if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress(sharedPreferences, context);
if (PreferencesUtils.isKey(R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, key)) {
String address = PreferencesUtils.getBluetoothRunningSpeedAndCadenceAddress();
connect(runningSpeedAndCadence, address);
}
@@ -113,16 +112,14 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public BluetoothRemoteSensorManager(Context context) {
this.context = context;
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
bluetoothAdapter = BluetoothUtils.getAdapter(context);
}
public void start() {
started = true;
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
//Trigger connection startup
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
//Registering triggers connection startup
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public synchronized void stop() {
@@ -134,7 +131,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
sensorDataSet.clear();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
started = false;
}
@@ -148,7 +145,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return;
}
if (PreferencesUtils.isBluetoothSensorAddressNone(context, address)) {
if (PreferencesUtils.isBluetoothSensorAddressNone(address)) {
Log.w(TAG, "No Bluetooth address.");
connectionManager.disconnect();
return;
@@ -1,4 +1,4 @@
package de.dennisguse.opentracks.util;
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
@@ -12,12 +12,12 @@ import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
public class AnnouncementUtils {
class AnnouncementUtils {
private AnnouncementUtils() {
}
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
Distance distance = trackStatistics.getTotalDistance();
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.services.tasks;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
@@ -35,8 +34,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
/**
@@ -103,8 +101,6 @@ public class VoiceAnnouncement {
}
};
private SharedPreferences sharedPreferences;
private TextToSpeech tts;
// Response from TTS after its initialization
private int ttsInitStatus = TextToSpeech.ERROR;
@@ -122,8 +118,6 @@ public class VoiceAnnouncement {
public void start() {
Log.d(TAG, "Start");
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (tts == null) {
tts = new TextToSpeech(context, status -> {
Log.i(TAG, "TextToSpeech initialized with status " + status);
@@ -161,9 +155,9 @@ public class VoiceAnnouncement {
}
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, track.getCategory());
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
boolean isMetricUnits = PreferencesUtils.isMetricUnits();
boolean isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval();
//TODO Do not load all trackpoints for every announcement
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
@@ -187,8 +181,6 @@ public class VoiceAnnouncement {
ttsFallback.release();
ttsFallback = null;
}
sharedPreferences = null;
}
private void onTtsReady() {
@@ -203,7 +195,7 @@ public class VoiceAnnouncement {
*/
}
tts.setLanguage(locale);
tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate(PreferencesUtils.getSharedPreferences(context), context));
tts.setSpeechRate(PreferencesUtils.getVoiceSpeedRate());
tts.setOnUtteranceProgressListener(utteranceListener);
}
}
@@ -17,7 +17,6 @@
package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
@@ -31,7 +30,6 @@ import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
/**
@@ -49,7 +47,7 @@ public class ActivityTypePreference extends DialogPreference {
setDialogIcon(null);
setPersistent(true);
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), ActivityTypePreference.this.getContext());
SummaryProvider<DialogPreference> summaryProvider = preference -> PreferencesUtils.getDefaultActivity();
setSummaryProvider(summaryProvider);
}
@@ -61,8 +59,6 @@ public class ActivityTypePreference extends DialogPreference {
public static class ActivityPreferenceDialog extends PreferenceDialogFragmentCompat {
private SharedPreferences sharedPreferences;
private AutoCompleteTextView textView;
private ImageView iconView;
@@ -80,11 +76,9 @@ public class ActivityTypePreference extends DialogPreference {
super.onBindDialogView(view);
final Context context = getActivity();
sharedPreferences = PreferencesUtils.getSharedPreferences(context);
textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, context);
String category = PreferencesUtils.getDefaultActivity();
textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
textView.setAdapter(adapter);
@@ -105,14 +99,8 @@ public class ActivityTypePreference extends DialogPreference {
updateIcon(TrackIconUtils.getIconValue(context, category));
}
@Override
public void onDestroy() {
super.onDestroy();
sharedPreferences = null;
}
private void showIconSelectDialog() {
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, getActivity());
String category = PreferencesUtils.getDefaultActivity();
ChooseActivityTypeDialogFragment.showDialog(getActivity().getSupportFragmentManager(), category);
}
@@ -121,13 +109,14 @@ public class ActivityTypePreference extends DialogPreference {
if (positiveResult) {
String newDefaultActivity = textView.getText().toString();
if (getPreference().callChangeListener(newDefaultActivity)) {
PreferencesUtils.setDefaultActivity(sharedPreferences, getActivity(), newDefaultActivity);
PreferencesUtils.setDefaultActivity(newDefaultActivity);
HackUtils.invalidatePreference(getPreference());
}
}
}
public void updateUI(String iconValue) {
updateIcon(iconValue);
textView.setText(getActivity().getString(TrackIconUtils.getIconActivityType(iconValue)));
textView.clearFocus();
}
@@ -0,0 +1,33 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
public class AnnouncementsSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_announcements);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_title);
}
@Override
public void onResume() {
super.onResume();
ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key));
voiceFrequency.setEntries(PreferencesUtils.getVoiceAnnouncementFrequencyEntries());
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries());
}
}
@@ -0,0 +1,83 @@
package de.dennisguse.opentracks.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.HackUtils;
public class DefaultsSettingsFragment extends PreferenceFragmentCompat implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
getActivity().runOnUiThread(this::updateUnits);
}
};
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_defaults);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_defaults_title);
}
@Override
public void onResume() {
super.onResume();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
updateUnits();
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog;
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits();
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(entries);
HackUtils.invalidatePreference(statsRatePreferences);
}
@Override
public void onChooseActivityTypeDone(String iconValue) {
if (activityPreferenceDialog != null) {
activityPreferenceDialog.updateUI(iconValue);
}
}
}
@@ -0,0 +1,78 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.util.StringUtils;
public class GpsSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_gps);
findPreference(getString(R.string.recording_distance_interval_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getRecordingDistanceInterval();
return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.max_recording_distance_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getMaxRecordingDistance();
return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.recording_gps_accuracy_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy();
return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.min_recording_interval_key))
.setSummaryProvider(
preference -> {
Duration interval = PreferencesUtils.getMinRecordingInterval();
return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds()));
}
);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_gps_title);
}
@Override
public void onResume() {
super.onResume();
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferencesUtils.getMinRecordingIntervalEntries());
ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key));
recordingDistanceInterval.setEntries(PreferencesUtils.getRecordingDistanceIntervalEntries());
ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key));
maxRecordingDistance.setEntries(PreferencesUtils.getMaxRecordingDistanceEntries());
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
recordingGpsAccuracy.setEntries(PreferencesUtils.getThresholdHorizontalAccuracyEntries());
}
}
@@ -0,0 +1,72 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_import_export);
setExportTrackFileFormatOptions();
setExportDirectorySummary();
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_import_export_title);
}
@Override
public void onResume() {
super.onResume();
setExportDirectorySummary();
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext()));
}
private void setExportTrackFileFormatOptions() {
final TrackFileFormat[] trackFileFormats = {
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES,
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA,
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA,
TrackFileFormat.GPX
};
String[] entries = new String[trackFileFormats.length];
String[] entryValues = new String[trackFileFormats.length];
for (int i = 0; i < entries.length; i++) {
TrackFileFormat trackFileFormat = trackFileFormats[i];
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
entries[i] = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId));
entryValues[i] = trackFileFormat.name();
}
ListPreference listPreference = findPreference(getString(R.string.export_trackfileformat_key));
listPreference.setEntries(entries);
listPreference.setEntryValues(entryValues);
}
private void setExportDirectorySummary() {
Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
instantExportDirectoryPreference.setSummaryProvider(preference -> {
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext());
//Use same value for not set as Androidx ListPreference and EditTextPreference
return directory != null ? directory.getName() : getString(R.string.not_set);
});
}
}
@@ -0,0 +1,132 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import android.util.Log;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
public class MainSettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = MainSettingsFragment.class.getSimpleName();
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private final Runnable bindServiceCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
}
service.getRecordingStatusObservable()
.observe(MainSettingsFragment.this, status -> onRecordingStatusChanged(status));
}
};
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback);
findPreference(getString(R.string.settings_defaults_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_defaults_key));
return true;
});
findPreference(getString(R.string.settings_ui_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_ui_key));
return true;
});
findPreference(getString(R.string.settings_gps_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_gps_key));
return true;
});
findPreference(getString(R.string.settings_sensors_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_sensors_key));
return true;
});
findPreference(getString(R.string.settings_announcements_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_announcements_key));
return true;
});
findPreference(getString(R.string.settings_import_export_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_import_export_key));
return true;
});
findPreference(getString(R.string.settings_open_tracks_key)).setOnPreferenceClickListener(preference -> {
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_open_tracks_key));
return true;
});
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
}
@Override
public void onResume() {
super.onResume();
trackRecordingServiceConnection.bind(getContext());
updatePrefsDependOnRecording();
}
@Override
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection.unbind(getContext());
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof ResetDialogPreference) {
dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey());
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
this.recordingStatus = status;
if (isAdded()) {
updatePrefsDependOnRecording();
}
}
private void updatePrefsDependOnRecording() {
Preference importExportPreference = findPreference(getString(R.string.settings_import_export_key));
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
boolean isRecording = recordingStatus.isRecording();
importExportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : getString(R.string.settings_import_export_summary));
importExportPreference.setEnabled(!isRecording);
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : getString(R.string.settings_reset_summary));
resetPreference.setEnabled(!isRecording);
}
}
@@ -0,0 +1,24 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import android.util.Log;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
public class OpenTracksSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_open_tracks);
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_open_tracks_title);
}
}
@@ -1,125 +0,0 @@
package de.dennisguse.opentracks.settings;
import android.content.Context;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
final class PreferenceHelper {
static String[] getMinRecordingIntervalEntries(Context context) {
String[] entryValues = context.getResources().getStringArray(R.array.min_recording_interval_values);
String[] entries = new String[entryValues.length];
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == PreferencesUtils.getMinRecordingIntervalDefault(context).getSeconds()) {
entries[i] = context.getString(R.string.value_smallest_recommended);
} else {
entries[i] = value < 60 ? context.getString(R.string.value_integer_second, value) : context.getString(R.string.value_integer_minute, value / 60);
}
}
return entries;
}
static String[] getRecordingDistanceIntervalEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values);
String[] entries = new String[entryValues.length];
final int recordingDistanceIntervalDefault = (int) PreferencesUtils.getRecordingDistanceIntervalDefault(context).toM();
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
if (value == recordingDistanceIntervalDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == recordingDistanceIntervalDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
static String[] getMaxRecordingDistanceEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.max_recording_distance_values);
String[] entries = new String[entryValues.length];
final int maxRecordingDistanceDefault = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
if (value == maxRecordingDistanceDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == maxRecordingDistanceDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
static String[] getRecordingGpsAccuracyEntries(Context context, boolean metricUnits) {
String[] entryValues = context.getResources().getStringArray(R.array.recording_gps_accuracy_values);
String[] entries = new String[entryValues.length];
final int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
final int recordingGPSAccuracyExcellent = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_excellent));
final int recordingGPSAccuracyPoor = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_poor));
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = context.getString(R.string.value_integer_meter, value);
if (value == recordingGPSAccuracyDefault) {
entries[i] = context.getString(R.string.value_integer_meter_recommended, value);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = context.getString(R.string.value_integer_meter_excellent_gps, value);
} else if (value == recordingGPSAccuracyPoor) {
entries[i] = context.getString(R.string.value_integer_meter_poor_gps, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = context.getString(R.string.value_integer_feet, feet);
if (value == recordingGPSAccuracyDefault) {
entries[i] = context.getString(R.string.value_integer_feet_recommended, feet);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = context.getString(R.string.value_integer_feet_excellent_gps, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
}
@@ -0,0 +1,34 @@
package de.dennisguse.opentracks.settings;
import de.dennisguse.opentracks.R;
class PreferencesOpenHelper {
private static final int PREFERENCES_VERSION = 1;
private PreferencesOpenHelper() {
}
static PreferencesOpenHelper newInstance() {
return new PreferencesOpenHelper();
}
void checkForUpgrade() {
int lastVersion = PreferencesUtils.getInt(R.string.prefs_last_version_key, 0);
if (PREFERENCES_VERSION > lastVersion) {
onUpgrade();
}
}
private void onUpgrade() {
PreferencesUtils.setInt(R.string.prefs_last_version_key, PREFERENCES_VERSION);
switch (PREFERENCES_VERSION) {
case 1:
upgradeFrom0to1();
}
}
private void upgradeFrom0to1() {
PreferencesUtils.setString(R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout());
}
}
@@ -0,0 +1,595 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.settings;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager;
import java.time.Duration;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.CsvConstants;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Utilities to access preferences stored in {@link SharedPreferences}.
*
* @author Jimmy Shih
*/
public class PreferencesUtils {
private final static String TAG = PreferencesUtils.class.getSimpleName();
private PreferencesUtils() {
}
private static SharedPreferences sharedPreferences;
private static Resources resources;
/**
* Must be called during application startup.
*/
public static void initPreferences(final Context context, final Resources resources) {
PreferencesUtils.resources = resources;
PreferencesUtils.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
PreferencesOpenHelper.newInstance().checkForUpgrade();
}
public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(changeListener);
changeListener.onSharedPreferenceChanged(sharedPreferences, null);
}
public static void unregisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(changeListener);
}
public static String getDefaultActivity() {
return getString(R.string.default_activity_key, resources.getString(R.string.default_activity_default));
}
public static void setDefaultActivity(String newDefaultActivity) {
setString(R.string.default_activity_key, newDefaultActivity);
}
/**
* Gets a preference key
*
* @param keyId the key id
*/
private static String getKey(int keyId) {
return resources.getString(keyId);
}
/**
* Compares if keyId and key belong to the same shared preference key.
*
* @param keyId The resource id of the key
* @param key The key of the preference
* @return true if key == null or key belongs to keyId
*/
public static boolean isKey(int keyId, String key) {
return key == null || key.equals(getKey(keyId));
}
public static boolean isKey(int[] keyIds, String key) {
for(int keyId : keyIds) {
if (isKey(keyId, key)) {
return true;
}
}
return false;
}
private static boolean getBoolean(int keyId, boolean defaultValue) {
return sharedPreferences.getBoolean(getKey(keyId), defaultValue);
}
static int getInt(int keyId, int defaultValue) {
try {
return sharedPreferences.getInt(getKey(keyId), defaultValue);
} catch (ClassCastException e) {
//Ignore
}
//NOTE: We assume that the data was stored as String due to use of ListPreference.
try {
String stringValue = sharedPreferences.getString(getKey(keyId), null);
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
return defaultValue;
}
}
private static float getFloat(int keyId, float defaultValue) {
try {
return sharedPreferences.getFloat(getKey(keyId), defaultValue);
} catch (ClassCastException e) {
//Ignore
}
//NOTE: We assume that the data was stored as String due to use of ListPreference.
try {
String stringValue = sharedPreferences.getString(getKey(keyId), null);
return Float.parseFloat(stringValue);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static String getString(int keyId, String defaultValue) {
return sharedPreferences.getString(getKey(keyId), defaultValue);
}
@VisibleForTesting
public static void setString(int keyId, String value) {
Editor editor = sharedPreferences.edit();
editor.putString(getKey(keyId), value);
editor.apply();
}
static void setInt(int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(keyId), value);
editor.apply();
}
public static boolean isMetricUnits() {
final String STATS_UNIT = resources.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(R.string.stats_units_key, STATS_UNIT));
}
public static void setMetricUnits(boolean metricUnits) {
String unit;
if (metricUnits) {
unit = resources.getString(R.string.stats_units_metric);
} else {
unit = resources.getString(R.string.stats_units_imperial);
}
setString( R.string.stats_units_key, unit);
}
public static boolean isReportSpeed(String category) {
final String STATS_RATE_DEFAULT = resources.getString(R.string.stats_rate_default);
String currentStatsRate = getString(R.string.stats_rate_key, STATS_RATE_DEFAULT);
if (currentStatsRate.equals(getString(R.string.stats_rate_speed_or_pace_default, STATS_RATE_DEFAULT))) {
return TrackIconUtils.isSpeedIcon(resources, category);
}
return currentStatsRate.equals(resources.getString(R.string.stats_rate_speed));
}
private static String getBluetoothSensorAddressNone() {
return resources.getString(R.string.sensor_type_value_none);
}
public static boolean isBluetoothSensorAddressNone(String currentValue) {
return getBluetoothSensorAddressNone().equals(currentValue);
}
public static String getBluetoothHeartRateSensorAddress() {
return getString(R.string.settings_sensor_bluetooth_heart_rate_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() {
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));
}
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 shouldShowStatsOnLockscreen() {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = resources.getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
}
public static boolean shouldKeepScreenOn() {
final boolean DEFAULT = resources.getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
return getBoolean(R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
}
public static boolean shouldUseFullscreen() {
final boolean DEFAULT = resources.getBoolean(R.bool.stats_fullscreen_while_recording_default);
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static Duration getVoiceAnnouncementFrequency() {
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
return Duration.ofSeconds(value);
}
static String[] getVoiceAnnouncementFrequencyEntries() {
String[] values = resources.getStringArray(R.array.voice_announcement_frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (resources.getString(R.string.announcement_off).equals(values[i])) {
options[i] = resources.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = resources.getString(R.string.value_integer_minute, Duration.ofSeconds(value).toMinutes());
}
}
return options;
}
/**
* @return Result depends on isMetricUnits
*/
public static Distance getVoiceAnnouncementDistance() {
final float DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_distance_default));
float value = getFloat(R.string.voice_announcement_distance_key, DEFAULT);
return Distance.one(isMetricUnits()).multipliedBy(value);
}
/**
* @return Result depends on isMetricUnits
*/
static String[] getVoiceAnnouncementDistanceEntries() {
String[] values = resources.getStringArray(R.array.voice_announcement_distance_values);
String[] options = new String[values.length];
boolean metricUnits = isMetricUnits();
for (int i = 0; i < values.length; i++) {
if (resources.getString(R.string.announcement_off).equals(values[i])) {
options[i] = resources.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = resources.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, value);
}
}
return options;
}
public static float getVoiceSpeedRate() {
final float DEFAULT = Float.parseFloat(resources.getString(R.string.voice_speed_rate_default));
return getFloat(R.string.voice_speed_rate_key, DEFAULT);
}
public static Distance getRecordingDistanceInterval() {
return Distance.of(getInt(R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal()));
}
public static Distance getRecordingDistanceIntervalDefault() {
return Distance.of(getRecordingDistanceIntervalDefaultInternal());
}
private static int getRecordingDistanceIntervalDefaultInternal() {
return Integer.parseInt(resources.getString(R.string.recording_distance_interval_default));
}
static String[] getRecordingDistanceIntervalEntries() {
String[] entryValues = resources.getStringArray(R.array.recording_distance_interval_values);
String[] entries = new String[entryValues.length];
final int recordingDistanceIntervalDefault = (int) getRecordingDistanceIntervalDefault().toM();
boolean metricUnits = isMetricUnits();
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
public static Distance getMaxRecordingDistance() {
final int MAX_RECORDING_DISTANCE = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
return Distance.of(getInt(R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE));
}
static String[] getMaxRecordingDistanceEntries() {
String[] entryValues = resources.getStringArray(R.array.max_recording_distance_values);
String[] entries = new String[entryValues.length];
final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
boolean metricUnits = isMetricUnits();
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
public static Duration getMinRecordingInterval() {
final Duration MIN_RECORDING_INTERVAL = getMinRecordingIntervalDefault();
return Duration.ofSeconds(getInt(R.string.min_recording_interval_key, (int) MIN_RECORDING_INTERVAL.getSeconds()));
}
public static Duration getMinRecordingIntervalDefault() {
return Duration.ofSeconds(Integer.parseInt(resources.getString(R.string.min_recording_interval_default)));
}
static String[] getMinRecordingIntervalEntries() {
String[] entryValues = resources.getStringArray(R.array.min_recording_interval_values);
String[] entries = new String[entryValues.length];
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
if (value == PreferencesUtils.getMinRecordingIntervalDefault().getSeconds()) {
entries[i] = resources.getString(R.string.value_smallest_recommended);
} else {
entries[i] = value < 60 ? resources.getString(R.string.value_integer_second, value) : resources.getString(R.string.value_integer_minute, value / 60);
}
}
return entries;
}
public static Distance getThresholdHorizontalAccuracy() {
final int RECORDING_GPS_ACCURACY = Integer.parseInt(resources.getString(R.string.recording_gps_accuracy_default));
return Distance.of(getInt(R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY));
}
static String[] getThresholdHorizontalAccuracyEntries() {
String[] entryValues = resources.getStringArray(R.array.recording_gps_accuracy_values);
String[] entries = new String[entryValues.length];
final int recordingGPSAccuracyDefault = Integer.parseInt(resources.getString(R.string.recording_gps_accuracy_default));
final int recordingGPSAccuracyExcellent = Integer.parseInt(resources.getString(R.string.recording_gps_accuracy_excellent));
final int recordingGPSAccuracyPoor = Integer.parseInt(resources.getString(R.string.recording_gps_accuracy_poor));
boolean metricUnits = isMetricUnits();
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = resources.getString(R.string.value_integer_meter_excellent_gps, value);
} else if (value == recordingGPSAccuracyPoor) {
entries[i] = resources.getString(R.string.value_integer_meter_poor_gps, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = resources.getString(R.string.value_integer_feet_excellent_gps, feet);
} else {
entries[i] = displayValue;
}
}
}
return entries;
}
public static boolean shouldInstantExportAfterWorkout(Context context) {
final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = resources.getBoolean(R.bool.post_workout_export_enabled_default);
return getBoolean(R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT) && isDefaultExportDirectoryUri(context);
}
public static TrackFileFormat getExportTrackFileFormat() {
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(R.string.export_trackfileformat_default, null);
String trackFileFormatName = getString(R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
try {
return TrackFileFormat.valueOf(trackFileFormatName);
} catch (Exception e) {
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
}
}
public static boolean getPreventReimportTracks() {
final boolean defaultValue = getBoolean(R.bool.import_prevent_reimport_default, false);
return getBoolean(R.string.import_prevent_reimport_key, defaultValue);
}
/**
* @return {@link androidx.appcompat.app.AppCompatDelegate}.MODE_*
*/
public static int getDefaultNightMode() {
final String defaultValue = getKey(R.string.night_mode_default);
final String value = getString(R.string.night_mode_key, defaultValue);
return Integer.parseInt(value);
}
public static void resetPreferences(Context context, boolean readAgain) {
if (readAgain) {
// We want to really clear settings now.
sharedPreferences.edit().clear().commit();
}
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
}
public static DocumentFile getDefaultExportDirectoryUri(Context context) {
String singleExportDirectorySettingsKey = getString(R.string.settings_default_export_directory_key, null);
if (singleExportDirectorySettingsKey == null) {
return null;
}
try {
return DocumentFile.fromTreeUri(context, Uri.parse(singleExportDirectorySettingsKey));
} catch (Exception e) {
Log.w(TAG, "Could not decode default export directory: " + e.getMessage());
}
return null;
}
public static void setDefaultExportDirectoryUri(Uri directoryUri) {
String value = directoryUri != null ? directoryUri.toString() : null;
setString(R.string.settings_default_export_directory_key, value);
}
public static boolean isDefaultExportDirectoryUri(Context context) {
return getDefaultExportDirectoryUri(context) != null;
}
public static int getLayoutColumns() {
return getInt(R.string.stats_custom_layout_columns_key, resources.getInteger(R.integer.stats_custom_layout_columns_default));
}
public static void setLayoutColumns(int columns) {
setInt(R.string.stats_custom_layout_columns_key, columns);
}
private static List<TypedArray> getMultiTypedArray(String key) {
List<TypedArray> typedArrays = new ArrayList<>();
try {
Class<R.array> resource = R.array.class;
Field field;
int i = 0;
do {
field = resource.getField(key + resources.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
typedArrays.add(resources.obtainTypedArray(field.getInt(null)));
i++;
} while (field != null); //TODO Catch no such field exception instead of using an endless loop.
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage());
}
return typedArrays;
}
@SuppressLint("ResourceType")
static String buildDefaultLayout() {
List<TypedArray> fieldsArrays = getMultiTypedArray("stats_custom_layout_fields_default_value");
return resources.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
+ CsvConstants.LINE_SEPARATOR;
}
public static Layout getCustomLayout() {
String csvCustomLayout = getString(R.string.stats_custom_layout_fields_key, buildDefaultLayout());
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
Layout layout = new Layout(csvParts.get(0));
for (int i = 1; i < csvParts.size(); i++) {
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
}
return layout;
}
public static void setCustomLayout(Layout layout) {
List<DataField> fields = layout.getFields();
if (fields.isEmpty()) {
return;
}
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
+ CsvConstants.LINE_SEPARATOR;
setString(R.string.stats_custom_layout_fields_key, csv);
}
public static void resetCustomLayoutPreferences() {
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_fields_key))) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(resources.getString(R.string.stats_custom_layout_fields_key));
editor.commit();
}
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_columns_key))) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(resources.getString(R.string.stats_custom_layout_columns_key));
editor.commit();
}
}
public static void applyNightMode() {
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode());
}
//TODO Check if resetPreferences can be used instead.
@Deprecated
@VisibleForTesting
public static void clear() {
sharedPreferences.edit().clear().commit();
}
}
@@ -10,7 +10,6 @@ import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
public class ResetDialogPreference extends DialogPreference {
@@ -46,7 +45,7 @@ public class ResetDialogPreference extends DialogPreference {
PreferencesUtils.resetPreferences(activity, true);
Toast.makeText(activity, R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
} else if (preferenceKey.equals(getString(R.string.settings_layout_reset_key))) {
PreferencesUtils.resetCustomLayoutPreferences(activity);
PreferencesUtils.resetCustomLayoutPreferences();
Toast.makeText(activity, R.string.settings_layout_reset_done, Toast.LENGTH_SHORT).show();
}
@@ -0,0 +1,71 @@
package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import android.util.Log;
import androidx.fragment.app.DialogFragment;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
import de.dennisguse.opentracks.util.BluetoothUtils;
public class SensorsSettingsFragment extends PreferenceFragmentCompat {
private final static String TAG = SensorsSettingsFragment.class.getSimpleName();
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_sensors);
setWheelCircumferenceInputFilter();
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_sensors_title);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof BluetoothLeHeartRatePreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES);
} else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID);
} else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID);
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
private void setWheelCircumferenceInputFilter() {
EditTextPreference wheelPreference = findPreference(getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key));
wheelPreference.setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof String) {
try {
int newValueInt = Integer.parseInt((String) newValue);
return newValueInt > 500 && newValueInt < 4000;
} catch (NumberFormatException e) {
Log.w(TAG, "Entered string is no number.");
}
}
return false;
});
}
}
@@ -1,50 +1,25 @@
package de.dennisguse.opentracks.settings;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import java.time.Duration;
import java.util.Locale;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference;
import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.HackUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback {
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
private static final String TAG = SettingsActivity.class.getSimpleName();
public static final String EXTRAS_CHECK_EXPORT_DIRECTORY = "Check Export Directory";
private PrefsFragment prefsFragment;
private boolean checkExportDirectory = false;
public static final String FRAGMENT_KEY = "fragmentKey";
private PreferenceFragmentCompat fragment = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -53,18 +28,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
if (intent != null && intent.hasExtra(EXTRAS_CHECK_EXPORT_DIRECTORY)) {
checkExportDirectory = true;
}
onReset();
}
@Override
protected View getRootView() {
return getLayoutInflater().inflate(R.layout.settings, null);
}
if (savedInstanceState != null) {
fragment = (PreferenceFragmentCompat) getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_KEY);
} else {
fragment = new MainSettingsFragment();
}
@Override
protected void setupActionBarBack(Toolbar toolbar) {
super.setupActionBarBack(toolbar);
toolbar.setTitle(R.string.menu_settings);
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).commit();
}
@Override
@@ -84,275 +55,50 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}
@Override
public void onChooseActivityTypeDone(String iconValue) {
prefsFragment.setDefaultActivity(iconValue);
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment);
}
@Override
public void onReset() {
prefsFragment = new PrefsFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit();
protected View getRootView() {
return getLayoutInflater().inflate(R.layout.settings, null);
}
public static class PrefsFragment extends PreferenceFragmentCompat {
private PreferenceFragmentCompat getPreferenceScreen(String key) {
PreferenceFragmentCompat fragment = null;
private SharedPreferences sharedPreferences;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(getActivity(), R.string.stats_units_key, key)) {
getActivity().runOnUiThread(this::updateUnits);
}
if (PreferencesUtils.isKey(getActivity(), R.string.night_mode_key, key)) {
getActivity().runOnUiThread(() -> ActivityUtils.applyNightMode(sharedPreferences, getContext()));
}
};
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
private final Runnable bindServiceCallback = new Runnable() {
@Override
public void run() {
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.w(TAG, "could not get TrackRecordingService");
return;
}
service.getRecordingStatusObservable()
.observe(PrefsFragment.this, status -> onRecordingStatusChanged(status));
}
};
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback);
try {
setPreferencesFromResource(R.xml.settings, rootKey);
} catch (ClassCastException e) {
// Some sharedPreference is broken: delete all and reset them; it is just a last resort...
Toast.makeText(getContext(), R.string.settings_error_initial_values_restored, Toast.LENGTH_LONG).show();
PreferencesUtils.resetPreferences(getContext(), true);
setPreferencesFromResource(R.xml.settings, rootKey);
}
setExportTrackFileFormatOptions();
setWheelCircumferenceInputFilter();
Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key));
instantExportDirectoryPreference.setSummaryProvider(preference -> {
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, getContext());
//Use same value for not set as Androidx ListPreference and EditTextPreference
return directory != null ? directory.getName() : getString(R.string.not_set);
});
findPreference(getString(R.string.recording_distance_interval_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
Distance distance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, getContext());
return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.max_recording_distance_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
Distance distance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, getContext());
return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.recording_gps_accuracy_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, getContext());
return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
}
);
findPreference(getString(R.string.min_recording_interval_key))
.setSummaryProvider(
preference -> {
Duration interval = PreferencesUtils.getMinRecordingInterval(sharedPreferences, getContext());
return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds()));
}
);
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
startActivity(intent);
return true;
});
if (key.equals(getString(R.string.settings_defaults_key))) {
fragment = new DefaultsSettingsFragment();
} else if (key.equals(getString(R.string.settings_ui_key))) {
fragment = new UserInterfaceSettingsFragment();
} else if (key.equals(getString(R.string.settings_gps_key))) {
fragment = new GpsSettingsFragment();
} else if (key.equals(getString(R.string.settings_sensors_key))) {
fragment = new SensorsSettingsFragment();
} else if (key.equals(getString(R.string.settings_announcements_key))) {
fragment = new AnnouncementsSettingsFragment();
} else if (key.equals(getString(R.string.settings_import_export_key))) {
fragment = new ImportExportSettingsFragment();
} else if (key.equals(getString(R.string.settings_open_tracks_key))) {
fragment = new OpenTracksSettingsFragment();
}
@Override
public void onResume() {
super.onResume();
return fragment;
}
trackRecordingServiceConnection.bind(getContext());
public PreferenceFragmentCompat openScreen(String key) {
fragment = getPreferenceScreen(key);
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).addToBackStack(key).commit();
return fragment;
}
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
updateUnits();
updatePrefsDependOnRecording();
updateBluetooth();
updatePostWorkoutExport();
}
private void updatePostWorkoutExport() {
Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key));
instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, getContext()));
}
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof ResetDialogPreference) {
dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey());
} else if (preference instanceof ActivityTypePreference) {
activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey());
dialogFragment = activityPreferenceDialog;
} else if (preference instanceof BluetoothLeHeartRatePreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES);
} else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID);
} else if (preference instanceof BluetoothLeCyclingPowerPreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID);
} else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) {
dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID);
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
@Override
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection.unbind(getContext());
sharedPreferences = null;
}
public void setDefaultActivity(String iconValue) {
if (activityPreferenceDialog != null) {
activityPreferenceDialog.updateUI(iconValue);
}
}
private void updatePrefsDependOnRecording() {
Preference resetPreference = findPreference(getString(R.string.settings_reset_key));
Preference importPreference = findPreference(getString(R.string.settings_import));
Preference exportPreference = findPreference(getString(R.string.settings_export));
boolean isRecording = recordingStatus.isRecording();
resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : "");
resetPreference.setEnabled(!isRecording);
importPreference.setEnabled(!isRecording);
exportPreference.setEnabled(!isRecording);
}
private void updateBluetooth() {
// Disable Bluetooth preference if device does not have Bluetooth
BluetoothLeSensorPreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
bluetoothPreference.setVisible(BluetoothUtils.hasBluetooth(getContext()));
}
private void updateUnits() {
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getActivity());
ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key));
voiceFrequency.setEntries(StringUtils.getAnnouncementFrequency(getActivity()));
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(StringUtils.getAnnouncementDistance(getActivity(), metricUnits));
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key));
recordingDistanceInterval.setEntries(PreferenceHelper.getRecordingDistanceIntervalEntries(getActivity(), metricUnits));
ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key));
maxRecordingDistance.setEntries(PreferenceHelper.getMaxRecordingDistanceEntries(getActivity(), metricUnits));
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits));
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
statsRatePreferences.setEntries(entries);
HackUtils.invalidatePreference(statsRatePreferences);
}
private void setExportTrackFileFormatOptions() {
final TrackFileFormat[] trackFileFormats = {
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES,
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA,
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA,
TrackFileFormat.GPX
};
String[] entries = new String[trackFileFormats.length];
String[] entryValues = new String[trackFileFormats.length];
for (int i = 0; i < entries.length; i++) {
TrackFileFormat trackFileFormat = trackFileFormats[i];
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos;
entries[i] = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId));
entryValues[i] = trackFileFormat.name();
}
ListPreference listPreference = findPreference(getString(R.string.export_trackfileformat_key));
listPreference.setEntries(entries);
listPreference.setEntryValues(entryValues);
}
private void setWheelCircumferenceInputFilter() {
EditTextPreference wheelPreference = findPreference(getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key));
wheelPreference.setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof String) {
try {
int newValueInt = Integer.parseInt((String) newValue);
return newValueInt > 500 && newValueInt < 4000;
} catch (NumberFormatException e) {
Log.w(TAG, "Entered string is no number.");
}
}
return false;
});
}
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
this.recordingStatus = status;
if (!status.isRecording() && isAdded()) {
updatePrefsDependOnRecording();
}
@Override
public void onChooseActivityTypeDone(String iconValue) {
try {
((ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller) this.fragment).onChooseActivityTypeDone(iconValue);
} catch (ClassCastException e) {
throw new ClassCastException(this.fragment.getClass().getSimpleName() + " must implement " + ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller.class.getSimpleName());
}
}
}
@@ -1,6 +1,5 @@
package de.dennisguse.opentracks.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
@@ -23,7 +22,6 @@ import de.dennisguse.opentracks.adapters.SettingsCustomLayoutAdapter;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatisticsUtils;
public class SettingsCustomLayoutActivity extends AbstractActivity implements SettingsCustomLayoutAdapter.SettingsCustomLayoutItemClickListener {
@@ -34,20 +32,18 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
private SettingsCustomLayoutAdapter adapterFieldsHidden;
private Layout layoutFieldsVisible;
private Layout layoutFieldsHidden;
private SharedPreferences sharedPreferences;
private int numColumns;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
// Recycler view with visible stats.
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), true);
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), true);
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, this);
numColumns = PreferencesUtils.getLayoutColumns();
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
gridLayoutManager = new GridLayoutManager(this, numColumns);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@@ -90,7 +86,7 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
numColumns = position + 1;
gridLayoutManager.setSpanCount(numColumns);
PreferencesUtils.setLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this, position + 1);
PreferencesUtils.setLayoutColumns(position + 1);
}
@Override
@@ -98,10 +94,10 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
}
});
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this) - 1);
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns() - 1);
// Recycler view with not visible stats.
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), false);
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), false);
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
@@ -115,14 +111,13 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
newLayout.addFields(layoutFieldsVisible.getFields());
newLayout.addFields(layoutFieldsHidden.getFields());
PreferencesUtils.setCustomLayout(sharedPreferences, this, newLayout);
PreferencesUtils.setCustomLayout(newLayout);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
layoutFieldsVisible = null;
}
@@ -0,0 +1,67 @@
package de.dennisguse.opentracks.settings;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R;
public class UserInterfaceSettingsFragment extends PreferenceFragmentCompat {
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(R.string.night_mode_key, key)) {
getActivity().runOnUiThread(PreferencesUtils::applyNightMode);
}
};
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_user_interface);
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
startActivity(intent);
return true;
});
}
@Override
public void onStart() {
super.onStart();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_ui_title);
}
@Override
public void onResume() {
super.onResume();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
DialogFragment dialogFragment = null;
if (preference instanceof ResetDialogPreference) {
dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey());
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName());
return;
}
super.onDisplayPreferenceDialog(preference);
}
}
@@ -1,5 +1,6 @@
package de.dennisguse.opentracks.settings.bluetooth;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
@@ -8,6 +9,8 @@ import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.text.TextUtils;
@@ -15,7 +18,10 @@ import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
@@ -29,7 +35,7 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.BluetoothLeAdapter;
import de.dennisguse.opentracks.util.BluetoothUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Preference to select a discoverable Bluetooth LE device.
@@ -85,7 +91,7 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
@Override
public CharSequence getSummary() {
if (getValue() == null || PreferencesUtils.isBluetoothSensorAddressNone(getContext(), getValue())) {
if (getValue() == null || PreferencesUtils.isBluetoothSensorAddressNone(getValue())) {
return getContext().getString(DEVICE_NONE_RESOURCEID);
}
@@ -143,13 +149,39 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
// Don't know why: need to load the drawable _twice_, so that animation is actually started.
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon.start();
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && (
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
)
) {
ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), granted -> {
if (!granted.containsValue(false)) {
startBluetoothScan();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
dismiss();
} else {
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
dismiss();
}
});
requestPermissionLauncher.launch(new String[]{Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN});
} else {
startBluetoothScan();
}
}
private void startBluetoothScan() {
List<ParcelUuid> serviceUUIDs = getArguments().getParcelableArrayList(ARG_BLE_SERVICE_UUIDS);
BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(getContext());
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Log.w(TAG, "Bluetooth adapter is present or not enabled.");
@@ -178,7 +178,9 @@ public class TrackStatisticsUpdater {
}
// Update moving time
currentSegment.addMovingTime(movingTime);
if (lastTrackPoint.isMoving()) {
currentSegment.addMovingTime(movingTime);
}
// Update max speed
if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) {
@@ -107,8 +107,4 @@ public class ActivityUtils {
vibrator.vibrate(milliseconds);
}
}
public static void applyNightMode(SharedPreferences sharedPreferences, Context context) {
AppCompatDelegate.setDefaultNightMode(PreferencesUtils.getDefaultNightMode(sharedPreferences, context));
}
}
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
@@ -23,16 +22,16 @@ import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.ExportService;
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class ExportUtils {
private static final String TAG = ExportUtils.class.getSimpleName();
public static void postWorkoutExport(Context context, Track.Id trackId, ExportServiceResultReceiver resultReceiver) {
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
if (PreferencesUtils.shouldInstantExportAfterWorkout(sharedPreferences, context)) {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, context);
if (PreferencesUtils.shouldInstantExportAfterWorkout(context)) {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(context);
ExportService.enqueue(context, resultReceiver, trackId, trackFileFormat, directory.getUri());
}
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.util;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -17,6 +16,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Create an {@link Intent} to request showing a Dashboard.
@@ -74,13 +74,11 @@ public class IntentDashboardUtils {
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
intent.putExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON, PreferencesUtils.shouldKeepScreenOn(sharedPreferences, context));
intent.putExtra(EXTRAS_SHOW_WHEN_LOCKED, PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, context));
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(sharedPreferences, context));
intent.putExtra(EXTRAS_SHOW_FULLSCREEN, PreferencesUtils.shouldUseFullscreen());
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -39,6 +39,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Utilities for creating intents.
@@ -91,7 +92,7 @@ public class IntentUtils {
continue;
}
Pair<Uri, String> uriTrackFile = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(PreferencesUtils.getSharedPreferences(context), context));
Pair<Uri, String> uriTrackFile = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat());
Pair<Uri, String> uriSharePicture = ShareContentProvider.createURI(trackId, track.getName(), TrackFileFormat.SHARE_PICTURE_PNG);
uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first));
@@ -1,41 +0,0 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import de.dennisguse.opentracks.R;
public class PreferencesOpenHelper {
private final Context context;
private final SharedPreferences sharedPreferences;
private static final int PREFERENCES_VERSION = 1;
private PreferencesOpenHelper(Context context, SharedPreferences sharedPreferences) {
this.context = context;
this.sharedPreferences = sharedPreferences;
}
public static PreferencesOpenHelper newInstance(Context context, SharedPreferences sharedPreferences) {
return new PreferencesOpenHelper(context, sharedPreferences);
}
public void checkForUpgrade() {
int lastVersion = PreferencesUtils.getInt(sharedPreferences, context, R.string.prefs_last_version_key, 0);
if (PREFERENCES_VERSION > lastVersion) {
onUpgrade();
}
}
private void onUpgrade() {
PreferencesUtils.setInt(sharedPreferences, context, R.string.prefs_last_version_key, PREFERENCES_VERSION);
switch (PREFERENCES_VERSION) {
case 1:
upgradeFrom0to1();
}
}
private void upgradeFrom0to1() {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout(context));
}
}
@@ -1,421 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.TypedArray;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager;
import java.time.Duration;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
/**
* Utilities to access preferences stored in {@link SharedPreferences}.
*
* @author Jimmy Shih
*/
public class PreferencesUtils {
private final static String TAG = PreferencesUtils.class.getSimpleName();
private PreferencesUtils() {
}
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
public static SharedPreferences getSharedPreferences(Context context) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
PreferencesOpenHelper.newInstance(context, sharedPreferences).checkForUpgrade();
return sharedPreferences;
}
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
}
public static void setDefaultActivity(SharedPreferences sharedPreferences, Context context, String newDefaultActivity) {
setString(sharedPreferences, context, R.string.default_activity_key, newDefaultActivity);
}
/**
* Gets a preference key
*
* @param context the context
* @param keyId the key id
*/
private static String getKey(Context context, int keyId) {
return context.getString(keyId);
}
/**
* Compares if keyId and key belong to the same shared preference key.
*
* @param keyId The resource id of the key
* @param key The key of the preference
* @return true if key == null or key belongs to keyId
*/
public static boolean isKey(Context context, int keyId, String key) {
return key == null || key.equals(getKey(context, keyId));
}
public static boolean isKey(Context context, int[] keyIds, String key) {
for(int keyId : keyIds) {
if (isKey(context, keyId, key)) {
return true;
}
}
return false;
}
private static boolean getBoolean(SharedPreferences sharedPreferences, Context context, int keyId, boolean defaultValue) {
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
try {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) {
//Ignore
}
//NOTE: We assume that the data was stored as String due to use of ListPreference.
try {
String stringValue = sharedPreferences.getString(getKey(context, keyId), null);
return Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
return defaultValue;
}
}
private static float getFloat(SharedPreferences sharedPreferences, Context context, int keyId, float defaultValue) {
try {
return sharedPreferences.getFloat(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) {
//Ignore
}
//NOTE: We assume that the data was stored as String due to use of ListPreference.
try {
String stringValue = sharedPreferences.getString(getKey(context, keyId), null);
return Float.parseFloat(stringValue);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static String getString(SharedPreferences sharedPreferences, Context context, int keyId, String defaultValue) {
return sharedPreferences.getString(getKey(context, keyId), defaultValue);
}
@VisibleForTesting
public static void setString(SharedPreferences sharedPreferences, Context context, int keyId, String value) {
Editor editor = sharedPreferences.edit();
editor.putString(getKey(context, keyId), value);
editor.apply();
}
static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value);
editor.apply();
}
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
}
public static void setMetricUnits(SharedPreferences sharedPreferences, Context context, boolean metricUnits) {
String unit;
if (metricUnits) {
unit = context.getString(R.string.stats_units_metric);
} else {
unit = context.getString(R.string.stats_units_imperial);
}
setString(sharedPreferences, context, R.string.stats_units_key, unit);
}
public static boolean isReportSpeed(SharedPreferences sharedPreferences, Context context, String category) {
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
String currentStatsRate = getString(sharedPreferences, context, R.string.stats_rate_key, STATS_RATE_DEFAULT);
if (currentStatsRate.equals(getString(sharedPreferences, context, R.string.stats_rate_speed_or_pace_default, STATS_RATE_DEFAULT))) {
return TrackIconUtils.isSpeedIcon(context, category);
}
return currentStatsRate.equals(context.getString(R.string.stats_rate_speed));
}
private static String getBluetoothSensorAddressNone(Context context) {
return context.getString(R.string.sensor_type_value_none);
}
public static boolean isBluetoothSensorAddressNone(Context context, String currentValue) {
return getBluetoothSensorAddressNone(context).equals(currentValue);
}
public static String getBluetoothHeartRateSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothCyclingCadenceSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_cadence_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothCyclingSpeedSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
}
public static Distance getWheelCircumference(SharedPreferences sharedPreferences, Context context) {
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
return Distance.ofMM(getInt(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT));
}
public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context));
}
public static String getBluetoothRunningSpeedAndCadenceAddress(SharedPreferences sharedPreferences, Context context) {
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_running_speed_and_cadence_key, getBluetoothSensorAddressNone(context));
}
public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(sharedPreferences, context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
}
public static boolean shouldKeepScreenOn(SharedPreferences sharedPreferences, Context context) {
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
return getBoolean(sharedPreferences, context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
}
public static boolean shouldUseFullscreen(SharedPreferences sharedPreferences, Context context) {
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_fullscreen_while_recording_default);
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static Duration getVoiceAnnouncementFrequency(SharedPreferences sharedPreferences, Context context) {
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_frequency_default));
int value = getInt(sharedPreferences, context, R.string.voice_announcement_frequency_key, DEFAULT);
return Duration.ofSeconds(value);
}
/**
* @return Result depends on isMetricUnits
*/
public static Distance getVoiceAnnouncementDistance(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_announcement_distance_default));
float value = getFloat(sharedPreferences, context, R.string.voice_announcement_distance_key, DEFAULT);
return Distance.one(isMetricUnits(sharedPreferences, context)).multipliedBy(value);
}
public static float getVoiceSpeedRate(SharedPreferences sharedPreferences, Context context) {
final float DEFAULT = Float.parseFloat(context.getResources().getString(R.string.voice_speed_rate_default));
return getFloat(sharedPreferences, context, R.string.voice_speed_rate_key, DEFAULT);
}
public static Distance getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
return Distance.of(getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal(context)));
}
public static Distance getRecordingDistanceIntervalDefault(Context context) {
return Distance.of(getRecordingDistanceIntervalDefaultInternal(context));
}
private static int getRecordingDistanceIntervalDefaultInternal(Context context) {
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
}
public static Distance getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) {
final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
return Distance.of(getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE));
}
public static Duration getMinRecordingInterval(SharedPreferences sharedPreferences, Context context) {
final Duration MIN_RECORDING_INTERVAL = getMinRecordingIntervalDefault(context);
Duration interval = Duration.ofSeconds(getInt(sharedPreferences, context, R.string.min_recording_interval_key, (int) MIN_RECORDING_INTERVAL.getSeconds()));
if (interval.isNegative()) {
// Due to removal of adaptive listener policy; used -1, and -2
interval = getMinRecordingIntervalDefault(context);
}
return interval;
}
public static Duration getMinRecordingIntervalDefault(Context context) {
return Duration.ofSeconds(Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
}
public static Distance getThresholdHorizontalAccuracy(SharedPreferences sharedPreferences, Context context) {
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
return Distance.of(getInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY));
}
public static boolean shouldInstantExportAfterWorkout(SharedPreferences sharedPreferences, Context context) {
final boolean INSTANT_POST_WORKOUT_EXPORT_DEFAULT = context.getResources().getBoolean(R.bool.post_workout_export_enabled_default);
return getBoolean(sharedPreferences, context, R.string.post_workout_export_enabled_key, INSTANT_POST_WORKOUT_EXPORT_DEFAULT) && isDefaultExportDirectoryUri(sharedPreferences, context);
}
public static TrackFileFormat getExportTrackFileFormat(SharedPreferences sharedPreferences, Context context) {
final String TRACKFILEFORMAT_NAME_DEFAULT = getString(sharedPreferences, context, R.string.export_trackfileformat_default, null);
String trackFileFormatName = getString(sharedPreferences, context, R.string.export_trackfileformat_key, TRACKFILEFORMAT_NAME_DEFAULT);
try {
return TrackFileFormat.valueOf(trackFileFormatName);
} catch (Exception e) {
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
}
}
public static boolean getPreventReimportTracks(SharedPreferences sharedPreferences, Context context) {
final boolean defaultValue = getBoolean(sharedPreferences, context, R.bool.import_prevent_reimport_default, false);
return getBoolean(sharedPreferences, context, R.string.import_prevent_reimport_key, defaultValue);
}
/**
* @return {@link androidx.appcompat.app.AppCompatDelegate}.MODE_*
*/
public static int getDefaultNightMode(SharedPreferences sharedPreferences, Context context) {
final String defaultValue = getKey(context, R.string.night_mode_default);
final String value = getString(sharedPreferences, context, R.string.night_mode_key, defaultValue);
return Integer.parseInt(value);
}
public static SharedPreferences resetPreferences(Context context, boolean readAgain) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
if (readAgain) {
// We want to really clear settings now.
sharedPreferences.edit().clear().commit();
}
PreferenceManager.setDefaultValues(context, R.xml.settings, readAgain);
return sharedPreferences;
}
public static DocumentFile getDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
String singleExportDirectorySettingsKey = getString(sharedPreferences, context, R.string.settings_default_export_directory_key, null);
if (singleExportDirectorySettingsKey == null) {
return null;
}
try {
return DocumentFile.fromTreeUri(context, Uri.parse(singleExportDirectorySettingsKey));
} catch (Exception e) {
Log.w(TAG, "Could not decode default export directory: " + e.getMessage());
}
return null;
}
public static void setDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context, Uri directoryUri) {
String value = directoryUri != null ? directoryUri.toString() : null;
setString(sharedPreferences, context, R.string.settings_default_export_directory_key, value);
}
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
}
public static int getLayoutColumns(SharedPreferences sharedPreferences, Context context) {
return getInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, context.getResources().getInteger(R.integer.stats_custom_layout_columns_default));
}
public static void setLayoutColumns(SharedPreferences sharedPreferences, Context context, int columns) {
setInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, columns);
}
private static List<TypedArray> getMultiTypedArray(Context context, String key) {
List<TypedArray> typedArrays = new ArrayList<>();
try {
Class<R.array> resource = R.array.class;
Field field;
int i = 0;
do {
field = resource.getField(key + context.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
typedArrays.add(context.getResources().obtainTypedArray(field.getInt(null)));
i++;
} while (field != null);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage());
}
return typedArrays;
}
@SuppressLint("ResourceType")
static String buildDefaultLayout(Context context) {
List<TypedArray> fieldsArrays = getMultiTypedArray(context, "stats_custom_layout_fields_default_value");
return context.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
+ CsvConstants.LINE_SEPARATOR;
}
public static Layout getCustomLayout(SharedPreferences sharedPreferences, Context context) {
String csvCustomLayout = getString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, buildDefaultLayout(context));
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
Layout layout = new Layout(csvParts.get(0));
for (int i = 1; i < csvParts.size(); i++) {
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
layout.addField(fieldParts[0], DataField.getTitleByKey(context, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(context.getString(R.string.stats_custom_layout_coordinates_key)));
}
return layout;
}
public static void setCustomLayout(SharedPreferences sharedPreferences, Context context, Layout layout) {
List<DataField> fields = layout.getFields();
if (fields.isEmpty()) {
return;
}
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR)) + CsvConstants.LINE_SEPARATOR;
setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, csv);
}
public static void resetCustomLayoutPreferences(Context context) {
SharedPreferences settings = getSharedPreferences(context);
if (settings.contains(context.getString(R.string.stats_custom_layout_fields_key))) {
SharedPreferences.Editor editor = settings.edit();
editor.remove(context.getString(R.string.stats_custom_layout_fields_key));
editor.commit();
}
if (settings.contains(context.getString(R.string.stats_custom_layout_columns_key))) {
SharedPreferences.Editor editor = settings.edit();
editor.remove(context.getString(R.string.stats_custom_layout_columns_key));
editor.commit();
}
}
}
@@ -77,7 +77,8 @@ public class PressureSensorUtils {
* https://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Internationale_H%C3%B6henformel
* {\color{White} p(h)} = p_0 \cdot \left( 1 - \frac{0{,}0065 \frac{\mathrm K}{\mathrm m} \cdot h}{T_0\ } \right)^{5{,}255}
*/
private static float getBarometricPressure(float altitude_m) {
@VisibleForTesting
public static float getBarometricPressure(float altitude_m) {
return (float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f));
}
}
@@ -8,15 +8,16 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class StatisticsUtils {
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_fastest_pace_key, statTitle)) {
if (PreferencesUtils.isKey(R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_fastest_pace_key, statTitle)) {
return context.getString(R.string.stats_empty_value_time);
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_distance_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_max_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_speed_key, statTitle)) {
} else if (PreferencesUtils.isKey(R.string.stats_custom_layout_distance_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_speed_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_speed_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_max_speed_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_speed_key, statTitle)) {
return context.getString(R.string.stats_empty_value_float);
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_coordinates_key, statTitle)) {
} else if (PreferencesUtils.isKey(R.string.stats_custom_layout_coordinates_key, statTitle)) {
return context.getString(R.string.stats_empty_value_coordinates);
} else {
return context.getString(R.string.stats_empty_value_integer);
@@ -204,7 +204,7 @@ public class StringUtils {
}
public static Pair<String, String> getHeartRateParts(Context context, Float heartrate_bpm) {
String value = context.getString(R.string.value_none);
String value = context.getString(R.string.value_unknown);
if (heartrate_bpm != null) {
value = StringUtils.formatDecimal(heartrate_bpm, 0);
}
@@ -213,7 +213,7 @@ public class StringUtils {
}
public static Pair<String, String> getCadenceParts(Context context, Float cadence_rpm) {
String value = context.getString(R.string.value_none);
String value = context.getString(R.string.value_unknown);
if (cadence_rpm != null) {
value = StringUtils.formatDecimal(cadence_rpm, 0);
}
@@ -222,7 +222,7 @@ public class StringUtils {
}
public static Pair<String, String> getPowerParts(Context context, Float power_w) {
String value = context.getString(R.string.value_none);
String value = context.getString(R.string.value_unknown);
if (power_w != null) {
value = StringUtils.formatDecimal(power_w, 0);
}
@@ -292,34 +292,6 @@ public class StringUtils {
}
}
public static String[] getAnnouncementFrequency(Context context) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_frequency_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = context.getString(R.string.value_integer_minute, value);
}
}
return options;
}
public static String[] getAnnouncementDistance(Context context, boolean metricUnits) {
String[] values = context.getResources().getStringArray(R.array.voice_announcement_distance_values);
String[] options = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (context.getString(R.string.announcement_off).equals(values[i])) {
options[i] = context.getString(R.string.value_off);
} else {
int value = Integer.parseInt(values[i]);
options[i] = context.getString(metricUnits ? R.string.value_integer_kilometer : R.string.value_integer_mile, value);
}
}
return options;
}
/**
* @return the formatted altitude_m (or null) and it's unit as {@link Pair}
*/
@@ -336,9 +308,24 @@ public class StringUtils {
}
public static String formatAltitude(Context context, Float altitude_m, boolean metricUnits) {
Pair<String, String> distanceParts = getAltitudeParts(context, altitude_m, metricUnits);
Pair<String, String> altitudeParts = getAltitudeParts(context, altitude_m, metricUnits);
return context.getString(R.string.altitude_with_unit, distanceParts.first, distanceParts.second);
return context.getString(R.string.altitude_with_unit, altitudeParts.first, altitudeParts.second);
}
/**
* @return the formatted altitudeDifference_m (or null) and it's unit as {@link Pair}
*/
public static Pair<String, String> getAltitudeChangeParts(Context context, Float altitudeDifference_m, boolean metricUnits) {
String formattedValue = altitudeDifference_m != null ? String.valueOf((int) Distance.of(altitudeDifference_m).toM_FT(metricUnits)) : context.getString(R.string.value_unknown);
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
return new Pair<>(formattedValue, unit);
}
public static String formatAltitudeChange(Context context, Float altitude_m, boolean metricUnits) {
Pair<String, String> altitudeParts = getAltitudeChangeParts(context, altitude_m, metricUnits);
return context.getString(R.string.altitude_with_unit, altitudeParts.first, altitudeParts.second);
}
public static String valueInParentheses(String text) {
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import androidx.annotation.NonNull;
@@ -155,55 +156,56 @@ public class TrackIconUtils {
*/
@NonNull
public static String getIconValue(Context context, String activityType) {
Resources resources = context.getResources();
if (activityType == null || activityType.equals("")) {
return UNKNOWN;
}
if (inList(context, activityType, AIRPLANE_LIST)) {
if (inList(resources, activityType, AIRPLANE_LIST)) {
return AIRPLANE;
}
if (inList(context, activityType, BIKE_LIST)) {
if (inList(resources, activityType, BIKE_LIST)) {
return BIKE;
}
if (inList(context, activityType, MOUNTAIN_BIKE_LIST)) {
if (inList(resources, activityType, MOUNTAIN_BIKE_LIST)) {
return MOUNTAIN_BIKE;
}
if (inList(context, activityType, CLIMBING_LIST)) {
if (inList(resources, activityType, CLIMBING_LIST)) {
return CLIMBING;
}
if (inList(context, activityType, MOTOR_BIKE_LIST)) {
if (inList(resources, activityType, MOTOR_BIKE_LIST)) {
return MOTOR_BIKE;
}
if (inList(context, activityType, KAYAKING_LIST)) {
if (inList(resources, activityType, KAYAKING_LIST)) {
return KAYAK;
}
if (inList(context, activityType, BOAT_LIST)) {
if (inList(resources, activityType, BOAT_LIST)) {
return BOAT;
}
if (inList(context, activityType, SAILING_LIST)) {
if (inList(resources, activityType, SAILING_LIST)) {
return SAILING;
}
if (inList(context, activityType, DRIVE_LIST)) {
if (inList(resources, activityType, DRIVE_LIST)) {
return DRIVE;
}
if (inList(context, activityType, INLINE_SKATING_LIST)) {
if (inList(resources, activityType, INLINE_SKATING_LIST)) {
return INLINE_SKATING;
}
if (inList(context, activityType, ESCOOTER_LIST)) {
if (inList(resources, activityType, ESCOOTER_LIST)) {
return ESCOOTER;
}
if (inList(context, activityType, RUN_LIST)) {
if (inList(resources, activityType, RUN_LIST)) {
return RUN;
}
if (inList(context, activityType, SKI_LIST)) {
if (inList(resources, activityType, SKI_LIST)) {
return SKI;
}
if (inList(context, activityType, SNOW_BOARDING_LIST)) {
if (inList(resources, activityType, SNOW_BOARDING_LIST)) {
return SNOW_BOARDING;
}
if (inList(context, activityType, SKATE_BOARDING_LIST)) {
if (inList(resources, activityType, SKATE_BOARDING_LIST)) {
return SKATE_BOARDING;
}
if (inList(context, activityType, WALK_LIST)) {
if (inList(resources, activityType, WALK_LIST)) {
return WALK;
}
return UNKNOWN;
@@ -216,7 +218,7 @@ public class TrackIconUtils {
* @param activityType the activity type
* @param list the list
*/
private static boolean inList(Context context, String activityType, int[] list) {
private static boolean inList(Resources context, String activityType, int[] list) {
for (int i : list) {
if (context.getString(i).equals(activityType)) {
return true;
@@ -228,10 +230,9 @@ public class TrackIconUtils {
/**
* Returns true if category is in the SPEED_ICON array. Otherwise returns false.
*
* @param context the context.
* @param category the name of the category, activity type.
*/
public static boolean isSpeedIcon(Context context, String category) {
return inList(context, category, SPEED_ICON);
public static boolean isSpeedIcon(Resources resources, String category) {
return inList(resources, category, SPEED_ICON);
}
}
@@ -26,6 +26,7 @@ import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Utilities for track name.
@@ -43,7 +44,7 @@ public class TrackNameUtils {
//TODO Should not access sharedPreferences; trackName should be an ENUM.
public static String getTrackName(Context context, Track.Id trackId, Instant startTime) {
String trackName = PreferencesUtils.getString(PreferencesUtils.getSharedPreferences(context), context, R.string.track_name_key, context.getString(R.string.track_name_default));
String trackName = PreferencesUtils.getString(R.string.track_name_key, context.getString(R.string.track_name_default));
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
return StringUtils.formatDateTime(context, startTime);
@@ -22,7 +22,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* This model is used to load intervals for a track.
@@ -46,7 +46,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
public IntervalStatisticsModel(@NonNull Application application) {
super(application);
minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(PreferencesUtils.getSharedPreferences(application), application);
minGPSDistance = PreferencesUtils.getRecordingDistanceInterval();
contentResolver = getApplication().getContentResolver();
handlerThread = new HandlerThread(TAG);
handlerThread.start();
@@ -18,6 +18,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
public class StatisticDataBuilder {
@@ -35,48 +36,53 @@ public class StatisticDataBuilder {
private static StatisticData build(@NonNull Context context, @NonNull TrackRecordingService.RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, boolean metricUnits) {
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
final TrackStatistics trackStatistics = recordingData.getTrackStatistics();
String title = null;
String description = null;
Pair<String, String> valueAndUnit = null;
boolean isWide = false;
final String sensorUnknown = context.getString(R.string.value_unknown);
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getTotalTime()), null);
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()), null);
title = context.getString(R.string.stats_total_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()), null);
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()), null);
title = context.getString(R.string.stats_moving_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
valueAndUnit = StringUtils.getDistanceParts(context, recordingData.getTrackStatistics().getTotalDistance(), metricUnits);
valueAndUnit = StringUtils.getDistanceParts(context, trackStatistics.getTotalDistance(), metricUnits);
title = context.getString(R.string.stats_distance);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
boolean reportSpeed = fieldKey.equals("speed");
title = fieldKey.equals("speed") ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null && sensorDataSet.getCyclingDistanceSpeed().hasValue() && sensorDataSet.getCyclingDistanceSpeed().isRecent()) {
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getCyclingDistanceSpeed().getValue().getSpeed(), metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_sensor, sensorDataSet.getCyclingDistanceSpeed().getSensorNameOrAddress());
title = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getSpeed().first, metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_sensor, sensorDataSet.getSpeed().second);
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_gps);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, true);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageMovingSpeed(), metricUnits, true);
title = context.getString(R.string.stats_average_moving_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, true);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageSpeed(), metricUnits, true);
title = context.getString(R.string.stats_average_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getMaxSpeed(), metricUnits, true);
title = context.getString(R.string.stats_max_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, false);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageMovingSpeed(), metricUnits, false);
title = context.getString(R.string.stats_average_moving_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, false);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageSpeed(), metricUnits, false);
title = context.getString(R.string.stats_average_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getMaxSpeed(), metricUnits, true);
title = context.getString(R.string.stats_fastest_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
@@ -84,10 +90,10 @@ public class StatisticDataBuilder {
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, metricUnits);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeGain(), metricUnits);
valueAndUnit = StringUtils.getAltitudeChangeParts(context, trackStatistics.getTotalAltitudeGain(), metricUnits);
title = context.getString(R.string.stats_gain);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeLoss(), metricUnits);
valueAndUnit = StringUtils.getAltitudeChangeParts(context, trackStatistics.getTotalAltitudeLoss(), metricUnits);
title = context.getString(R.string.stats_loss);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
title = context.getString(R.string.stats_coordinates);
@@ -95,34 +101,41 @@ public class StatisticDataBuilder {
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
} else {
valueAndUnit = new Pair<>(context.getString(R.string.value_none), null);
valueAndUnit = new Pair<>(context.getString(R.string.value_unknown), null);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
title = context.getString(R.string.stats_sensors_heart_rate);
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().getValue());
description = sensorDataSet.getHeartRate().getSensorNameOrAddress();
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().first);
description = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(context, null);
description = context.getString(R.string.value_none);
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
title = context.getString(R.string.stats_sensors_cadence);
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
valueAndUnit = StringUtils.getCadenceParts(context, sensorDataSet.getCyclingCadence().getValue());
description = sensorDataSet.getCyclingCadence().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(context, null);
description = context.getString(R.string.value_none);
Float cadence = null;
if (sensorDataSet != null) {
Pair<Float, String> cadenceData = sensorDataSet.getCadence();
if (cadenceData != null) {
cadence = cadenceData.first;
description = cadenceData.second;
}
}
valueAndUnit = StringUtils.getCadenceParts(context, cadence);
if (description == null) {
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
title = context.getString(R.string.stats_sensors_power);
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getPowerParts(context, null);
description = context.getString(R.string.value_none);
description = sensorUnknown;
}
}
@@ -134,7 +147,7 @@ public class StatisticDataBuilder {
}
/**
* Builds a list of StatisticData with sensors connected but not in statisticDataList.
* Builds a list of StatisticData with sensors configured but not in statisticDataList.
*/
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, TrackRecordingService.RecordingData recordingData, List<StatisticData> statisticDataList, boolean metricUnits) {
List<StatisticData> sensorDataList = new ArrayList<>();
@@ -142,13 +155,13 @@ public class StatisticDataBuilder {
if (sensorDataSet == null) {
return sensorDataList;
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null) {
sensorDataList.add(build(context, recordingData, "heart_rate", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCadence() != null) {
sensorDataList.add(build(context, recordingData, "cadence", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
sensorDataList.add(build(context, recordingData, "power", true, metricUnits));
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z"/>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More