Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e77590a125 | |||
| ca330b2d80 | |||
| 20f1524701 | |||
| 96b2a15643 | |||
| ad59392e02 | |||
| 29f92b887c | |||
| 46328211c2 | |||
| 3760130d1f | |||
| 1094827fac | |||
| 36fcb44af5 | |||
| a683fbac4b | |||
| 64ad9019d1 | |||
| 1e80d5c8a8 | |||
| ac765b1ee8 | |||
| 850c4f0a9a | |||
| 4c32999752 | |||
| 76823b167a | |||
| 9994cfd260 | |||
| 1750236474 | |||
| fed6e31f9e | |||
| 9bafc7d4a1 | |||
| be58b48d20 | |||
| 303abac980 | |||
| d1a459efc2 | |||
| 96069e6f09 | |||
| 301a721329 | |||
| e8c6801a32 | |||
| 355f3ef614 | |||
| eaa6083586 | |||
| a0c5d02c51 | |||
| 76f9f5fc82 | |||
| 8e2598e93b |
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
liberapay: dennis.guse
|
||||
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
||||
@@ -73,8 +73,12 @@ captures/
|
||||
.idea/workspace.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following line if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
*.jks
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# Fastlane
|
||||
Appfile
|
||||
releasePlayStore/*
|
||||
Gemfile.lock
|
||||
@@ -19,25 +19,26 @@ android:
|
||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-28
|
||||
- android-29
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
#install:
|
||||
# - echo y | sdkmanager "platform-tools"
|
||||
# - echo y | sdkmanager --channel=4 "emulator"
|
||||
# - echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
# - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
# - emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
# - android-wait-for-emulator
|
||||
# - adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew clean build
|
||||
- ./gradlew connectedCheck
|
||||
# - ./gradlew connectedCheck
|
||||
|
||||
sbefore_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OpenTrack: Developer information
|
||||
# OpenTracks: Developer information
|
||||
|
||||
## Create releases for FDroid
|
||||
## Craft new releases
|
||||
|
||||
1. Get next _version code_ (master branch): `git rev-list HEAD --count master`
|
||||
2. Decide on _version name_ (semantic versioning)
|
||||
@@ -9,7 +9,27 @@
|
||||
5. Create commit with all changes
|
||||
6. Tag newly create commit with _version name_ (e.g., v3.2.1)
|
||||
7. Push commits and tags to public repository
|
||||
8. Add changelog to tag (Github releases)
|
||||
8. Add changelog to tag (Github releases)
|
||||
|
||||
## F-Droid
|
||||
Application id: de.dennisguse.opentracks
|
||||
|
||||
It if it properly tagged F-Droid takes care of building the app and publishing.
|
||||
Usually, it takes two days before the update is published (build, signed, and published).
|
||||
|
||||
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
|
||||
|
||||
## Google Play Store
|
||||
Application id: de.dennisguse.opentracks.playstore
|
||||
|
||||
Requirement: `gem install fastlane`
|
||||
|
||||
1. Update store meta data (e.g., description and screenshots)
|
||||
`fastlane supply --version_code $(git rev-list HEAD --count master) --skip_upload_apk`
|
||||
(requires API key)
|
||||
2. Build signed apk
|
||||
(requires keystore)
|
||||
3. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
||||
|
||||
## Translations (localization)
|
||||
|
||||
|
||||
@@ -2,9 +2,30 @@
|
||||
|
||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||
|
||||
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="60">
|
||||
</a>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Free</th>
|
||||
<th>Paid</th>
|
||||
<th>Donations</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://f-droid.org/packages/de.dennisguse.opentracks">
|
||||
<img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="60" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">
|
||||
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="60" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="https://liberapay.com/dennis.guse/donate">
|
||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Features:
|
||||
* __Tracking:__ track your sport and outdoor activities
|
||||
@@ -26,9 +47,6 @@ _Please note:_
|
||||
_Tracks_ does not contain a _map_ (as this would required Internet access).
|
||||
To show your tracks on a map, a 3rd party application (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand) or [MAPS.ME](https://play.google.com/store/apps/details?id=com.mapswithme.maps.pro)) needs to be installed.
|
||||
|
||||
<a href="https://liberapay.com/dennis.guse/donate">
|
||||
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45">
|
||||
</a>
|
||||
|
||||
## Screenshots
|
||||
<div>
|
||||
|
||||
@@ -74,6 +74,11 @@ android {
|
||||
release {
|
||||
minifyEnabled false
|
||||
}
|
||||
|
||||
releasePlayStore {
|
||||
applicationIdSuffix ".playstore"
|
||||
versionNameSuffix "-PlayStore"
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -43,7 +43,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -41,7 +41,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -33,7 +33,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -39,7 +39,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -18,7 +18,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -21,7 +21,7 @@
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,38 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
#
|
||||
# For a list of all available actions, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/actions
|
||||
#
|
||||
# For a list of all available plugins, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/plugins/available-plugins
|
||||
#
|
||||
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(task: "test")
|
||||
end
|
||||
|
||||
desc "Submit a new Beta Build to Crashlytics Beta"
|
||||
lane :beta do
|
||||
gradle(task: "clean assembleRelease")
|
||||
crashlytics
|
||||
|
||||
# sh "your_script.sh"
|
||||
# You can also use other beta testing services here
|
||||
end
|
||||
|
||||
desc "Deploy a new version to the Google Play"
|
||||
lane :deploy do
|
||||
gradle(task: "clean assembleRelease")
|
||||
upload_to_play_store
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.2.9: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Option to keep screen on while recording
|
||||
- Markers: removed split markers completely
|
||||
- Voice announcement now work without READ_PHONE_STATE permission
|
||||
|
||||
Bugfixes:
|
||||
- Statistics: heart rate was not shown
|
||||
- Statistics: time was briefly reset to 0s
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.2.10: OpenTracks
|
||||
|
||||
Changes:
|
||||
- ChartView is not configurable anymore
|
||||
- After reboot: removed automatic restart of an unfinished recording
|
||||
|
||||
Bugfix:
|
||||
- Speech announcements use MAY_DUCK (again)
|
||||
- ChartView shows markers (again)
|
||||
- Fixed landscape layout of TrackListActivity
|
||||
- Request legacy file API behavior (necessary for Android 10)
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 125 KiB |
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningServiceInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
|
||||
/**
|
||||
* Tests for the BootReceiver.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BootReceiverTest {
|
||||
private static final String SERVICE_NAME = "de.dennisguse.opentracks.services.TrackRecordingService";
|
||||
|
||||
private Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
|
||||
/**
|
||||
* Tests the behavior when receive notification which is the phone boot.
|
||||
*/
|
||||
@Test
|
||||
public void testOnReceive_startService() {
|
||||
// Make sure no TrackRecordingService
|
||||
Intent stopIntent = new Intent(context, TrackRecordingService.class);
|
||||
context.stopService(stopIntent);
|
||||
Assert.assertFalse(isServiceExisted(context));
|
||||
|
||||
BootReceiver bootReceiver = new BootReceiver();
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_BOOT_COMPLETED);
|
||||
bootReceiver.onReceive(context, intent);
|
||||
// Check if the service is started
|
||||
Assert.assertFalse(isServiceExisted(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior when receive notification which is not the phone boot.
|
||||
*/
|
||||
@Test
|
||||
public void testOnReceive_noStartService() {
|
||||
// Make sure no TrackRecordingService
|
||||
Intent stopIntent = new Intent(context, TrackRecordingService.class);
|
||||
context.stopService(stopIntent);
|
||||
Assert.assertFalse(isServiceExisted(context));
|
||||
|
||||
BootReceiver bootReceiver = new BootReceiver();
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_BUG_REPORT);
|
||||
bootReceiver.onReceive(context, intent);
|
||||
|
||||
// Check if the service is not started
|
||||
Assert.assertFalse(isServiceExisted(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a service is started in a context.
|
||||
*
|
||||
* @param context the context for checking a service
|
||||
*/
|
||||
private boolean isServiceExisted(Context context) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
Assert.assertNotNull(activityManager);
|
||||
|
||||
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||
|
||||
for (int i = 0; i < serviceList.size(); i++) {
|
||||
RunningServiceInfo serviceInfo = serviceList.get(i);
|
||||
ComponentName componentName = serviceInfo.service;
|
||||
if (componentName.getClassName().equals(SERVICE_NAME)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -71,17 +71,12 @@ public class TrackRecordingServiceTest {
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils providerUtils;
|
||||
|
||||
private long trackId = Math.abs(new Random().nextLong());
|
||||
private final long trackId = Math.abs(new Random().nextLong());
|
||||
|
||||
static Intent createStartIntent(Context context) {
|
||||
return new Intent(context, TrackRecordingService.class);
|
||||
}
|
||||
|
||||
static void updateAutoResumePrefs(Context context, int attempts, int timeoutMins) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, attempts);
|
||||
PreferencesUtils.setString(context, R.string.auto_resume_track_timeout_key, "" + timeoutMins);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Set up the mock content resolver
|
||||
@@ -95,9 +90,6 @@ public class TrackRecordingServiceTest {
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Disable auto resume by default.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
@@ -128,124 +120,6 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertNotNull(service);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_shouldResume() throws Exception {
|
||||
// Insert a dummy track and mark it as recording track.
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We expect to resume the previous track.
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_simulateReboot() throws Exception {
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
// Simulate recording a track.
|
||||
long id = service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertEquals(id, service.getRecordingTrackId());
|
||||
mServiceRule.unbindService();
|
||||
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
Assert.assertTrue(service.isRecording());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
||||
// Insert a dummy track and mark it as recording track.
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), false);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it was stopped.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(-1L, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
||||
// Insert a dummy track last updated 20 min ago.
|
||||
createDummyTrack(trackId, System.currentTimeMillis() - 1500 * 60 * 1000, true);
|
||||
|
||||
// Clear the number of attempts and set the timeout to 10 min.
|
||||
updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because it has expired.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
||||
// Insert a dummy track.
|
||||
createDummyTrack(trackId, System.currentTimeMillis(), true);
|
||||
|
||||
// Set the number of attempts to max.
|
||||
updateAutoResumePrefs(context, TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default)));
|
||||
|
||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
||||
Intent startIntent = createStartIntent(context);
|
||||
startIntent.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
mServiceRule.startService(startIntent);
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(startIntent));
|
||||
|
||||
//then
|
||||
Assert.assertNotNull(service);
|
||||
|
||||
// We don't expect to resume the previous track, because there were already too many attempts.
|
||||
Assert.assertFalse(service.isRecording());
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_noTracks() throws Exception {
|
||||
@@ -289,16 +163,18 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
long trackId = service.getRecordingTrackId();
|
||||
|
||||
long newTrackId = service.startNewTrack();
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
|
||||
|
||||
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
Assert.assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -314,44 +190,7 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertFalse(service.isRecording());
|
||||
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertEquals(-1L, waypointId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
Assert.assertTrue(service.isRecording());
|
||||
Assert.assertFalse(service.isPaused());
|
||||
insertLocation(service);
|
||||
|
||||
long waypointId1 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId1);
|
||||
long waypointId2 = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
Assert.assertNotEquals(-1L, waypointId2);
|
||||
|
||||
Waypoint wpt = providerUtils.getWaypoint(waypointId1);
|
||||
Assert.assertEquals(context.getString(R.string.marker_statistics_icon_url), wpt.getIcon());
|
||||
Assert.assertEquals(context.getString(R.string.marker_split_name_format, 0), wpt.getName());
|
||||
Assert.assertEquals(WaypointType.STATISTICS, wpt.getType());
|
||||
Assert.assertEquals(trackId, wpt.getTrackId());
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNotNull(wpt.getTripStatistics());
|
||||
// TODO check the rest of the params.
|
||||
|
||||
// TODO: Check waypoint 2.
|
||||
}
|
||||
|
||||
// NOTE: Do not use to create a track that is currently recording.
|
||||
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
@@ -399,12 +238,12 @@ public class TrackRecordingServiceTest {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
|
||||
ITrackRecordingService service = ((ITrackRecordingService) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
Assert.assertTrue(service.isRecording());
|
||||
insertLocation(service);
|
||||
|
||||
long trackId = service.getRecordingTrackId();
|
||||
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
Assert.assertNotEquals(-1L, waypointId);
|
||||
Waypoint wpt = providerUtils.getWaypoint(waypointId);
|
||||
@@ -415,5 +254,7 @@ public class TrackRecordingServiceTest {
|
||||
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
|
||||
Assert.assertNotNull(wpt.getLocation());
|
||||
Assert.assertNull(wpt.getTripStatistics());
|
||||
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Disable auto resume by default.
|
||||
TrackRecordingServiceTest.updateAutoResumePrefs(context, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT, 0);
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
@@ -151,27 +148,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, Integer.parseInt(context.getResources().getString(R.string.split_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_splitFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.split_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
@@ -242,9 +218,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setBearing(3.0f);
|
||||
service.insertTrackPoint(location);
|
||||
|
||||
if (i % 10 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
} else if (i % 7 == 0) {
|
||||
if (i % 7 == 0) {
|
||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ limitations under the License.
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3162"
|
||||
android:versionName="v3.2.8"> <!-- For FDroid. -->
|
||||
android:versionCode="3195"
|
||||
android:versionName="v3.2.10"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<application
|
||||
android:name=".Startup"
|
||||
@@ -25,6 +25,7 @@ limitations under the License.
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<!-- Activities -->
|
||||
@@ -83,9 +84,7 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".TrackEditActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackListActivity"
|
||||
android:screenOrientation="userPortrait">
|
||||
<activity android:name=".TrackListActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -101,7 +100,6 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
|
||||
<!-- Services -->
|
||||
<provider
|
||||
android:name=".content.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
@@ -118,14 +116,6 @@ limitations under the License.
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Receivers -->
|
||||
<receiver android:name=".BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Providers -->
|
||||
<service
|
||||
android:name=".services.TrackRecordingService"
|
||||
android:enabled="true"
|
||||
@@ -145,44 +135,23 @@ limitations under the License.
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
<!-- Permissions to access sensors -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.faketouch"
|
||||
android:required="false" />
|
||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.compass"
|
||||
android:required="false" />
|
||||
<!-- Permission to read/write external storage -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony.gsm"
|
||||
android:required="false" />
|
||||
<!-- Required features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
<!-- Optional features -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen.multitouch"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
</manifest>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
|
||||
import static android.content.Intent.ACTION_BOOT_COMPLETED;
|
||||
|
||||
/**
|
||||
* This class react to the BOOT_COMPLETED broadcast.
|
||||
* <p>
|
||||
* One example of a broadcast message that this class is interested in,
|
||||
* is notification about the phone boot. We may want to resume a previously
|
||||
* started tracking session if the phone crashed (hopefully not), or the user
|
||||
* decided to swap the battery or some external event occurred which forced
|
||||
* a phone reboot.
|
||||
* <p>
|
||||
* This class simply delegates to {@link TrackRecordingService} to make a
|
||||
* decision whether to continue with the previous track (if any), or just
|
||||
* abandon it.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = BootReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction());
|
||||
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Intent startIntent = new Intent(context, TrackRecordingService.class)
|
||||
.putExtra(TrackRecordingService.RESUME_TRACK_EXTRA_NAME, true);
|
||||
context.startService(startIntent);
|
||||
} else {
|
||||
Log.w(TAG, "BootReceiver: unsupported action");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,8 +71,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
||||
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
||||
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
||||
this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||
waypointMarkerType.setAdapter(adapter);
|
||||
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ import androidx.loader.content.Loader;
|
||||
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -74,8 +74,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||
* class. Anonymous inner class will get garbage collected.
|
||||
*/
|
||||
private final OnSharedPreferenceChangeListener
|
||||
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
// Note that the key can be null
|
||||
@@ -152,7 +151,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
|
||||
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
||||
@@ -161,13 +159,13 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
||||
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
||||
|
||||
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS;
|
||||
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin;
|
||||
int iconId = MarkerUtils.ICON_ID;
|
||||
String name = cursor.getString(nameIndex);
|
||||
long time = cursor.getLong(timeIndex);
|
||||
String category = statistics ? null : cursor.getString(categoryIndex);
|
||||
String description = statistics ? null : cursor.getString(descriptionIndex);
|
||||
String category = cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
String photoUrl = cursor.getString(photoUrlIndex);
|
||||
//TODO also show latitude and longitude in list
|
||||
double latitude = cursor.getDouble(latitudeIndex);
|
||||
double longitude = cursor.getDouble(longitudeIndex);
|
||||
|
||||
@@ -246,7 +244,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (track != null && item.getItemId() == R.id.marker_list_insert_marker) {
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, track.getId());
|
||||
|
||||
@@ -46,7 +46,6 @@ import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
|
||||
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
@@ -55,6 +54,7 @@ import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -393,11 +393,9 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
}
|
||||
}
|
||||
|
||||
boolean statistics = waypoint.getType() == WaypointType.STATISTICS;
|
||||
|
||||
resultMap.put(IS_RECORDING_FIELD, false);
|
||||
resultMap.put(IS_PAUSED_FIELD, true);
|
||||
resultMap.put(ICON_ID_FIELD, statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin);
|
||||
resultMap.put(ICON_ID_FIELD, MarkerUtils.ICON_ID);
|
||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
|
||||
resultMap.put(NAME_FIELD, waypoint.getName());
|
||||
// Display the marker's track name in the total time field
|
||||
@@ -405,8 +403,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, null);
|
||||
resultMap.put(MARKER_COUNT_FIELD, 0);
|
||||
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
||||
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
|
||||
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription());
|
||||
resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
|
||||
resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
|
||||
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
||||
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
||||
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
|
||||
|
||||
@@ -55,7 +55,6 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StatsUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -118,25 +117,31 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
});
|
||||
}
|
||||
};
|
||||
/*
|
||||
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
|
||||
* class. Anonymous inner class will get garbage collected.
|
||||
*/
|
||||
|
||||
// Note that sharedPreferenceChangeListener cannot be an anonymous inner class. Anonymous inner class will get garbage collected.
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||
setLockscreenPolicy();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
if (key == null) return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -227,11 +232,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -242,6 +249,17 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackDetailActivity.this)
|
||||
&& PreferencesUtils.isRecording(this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -413,22 +431,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track id.
|
||||
*/
|
||||
// TODO Seems to be no used.
|
||||
@Deprecated
|
||||
public long getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
private void setBluetoothHeartRateSensorName(String sensorName) {
|
||||
StatsUtils.setBluetoothHeartRateSensorName(this, sensorName, isRecording());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the data in the intent.
|
||||
*/
|
||||
private void handleIntent(Intent intent) {
|
||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
|
||||
@@ -164,7 +164,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
private MenuItem deleteAllMenuItem;
|
||||
// private MenuItem deleteAllMenuItem;
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -371,7 +371,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
||||
|
||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -425,9 +424,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_list_delete_all:
|
||||
deleteTracks(new long[]{-1L});
|
||||
return true;
|
||||
case R.id.track_list_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -495,9 +491,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
|
||||
}
|
||||
}
|
||||
if (deleteAllMenuItem != null) {
|
||||
deleteAllMenuItem.setVisible(hasTrack && !isRecording);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,6 @@ import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
@@ -203,7 +201,6 @@ class ChartValueSeries {
|
||||
/**
|
||||
* Gets the interval value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
@@ -218,7 +215,6 @@ class ChartValueSeries {
|
||||
/**
|
||||
* Gets the max marker value.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
int getMaxMarkerValue() {
|
||||
return maxMarkerValue;
|
||||
}
|
||||
|
||||
@@ -37,12 +37,14 @@ import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -55,10 +57,10 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*/
|
||||
public class ChartView extends View {
|
||||
|
||||
public static final float MEDIUM_TEXT_SIZE = 18f;
|
||||
public static final float SMALL_TEXT_SIZE = 12f;
|
||||
static final float MEDIUM_TEXT_SIZE = 18f;
|
||||
static final float SMALL_TEXT_SIZE = 12f;
|
||||
|
||||
public static final int Y_AXIS_INTERVALS = 5;
|
||||
static final int Y_AXIS_INTERVALS = 5;
|
||||
|
||||
public static final int NUM_SERIES = 6;
|
||||
public static final int ELEVATION_SERIES = 0;
|
||||
@@ -79,19 +81,24 @@ public class ChartView extends View {
|
||||
private static final int SPACER = 4;
|
||||
private static final int Y_AXIS_OFFSET = 16;
|
||||
|
||||
//TODO Determine from actual size of the used drawable
|
||||
private static final float WAYPOINT_X_ANCHOR = 13f / 48f;
|
||||
|
||||
static {
|
||||
X_FRACTION_FORMAT.setMaximumFractionDigits(1);
|
||||
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
|
||||
}
|
||||
|
||||
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
|
||||
private final ArrayList<double[]> chartData = new ArrayList<>();
|
||||
private final ArrayList<Waypoint> waypoints = new ArrayList<>();
|
||||
private final List<double[]> chartData = new ArrayList<>();
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
|
||||
private final Paint axisPaint;
|
||||
private final Paint xAxisMarkerPaint;
|
||||
private final Paint gridPaint;
|
||||
private final Paint markerPaint;
|
||||
private final Drawable pointer;
|
||||
private final Drawable waypointMarker;
|
||||
private final int markerWidth;
|
||||
private final int markerHeight;
|
||||
private final Scroller scroller;
|
||||
@@ -145,7 +152,7 @@ public class ChartView extends View {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent event) {
|
||||
// Check if the y event is within markerHeight of the marker center
|
||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2) < markerHeight) {
|
||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
Waypoint nearestWaypoint = null;
|
||||
synchronized (waypoints) {
|
||||
@@ -255,7 +262,7 @@ public class ChartView extends View {
|
||||
gridPaint.setAntiAlias(false);
|
||||
gridPaint.setPathEffect(new DashPathEffect(new float[]{3, 2}, 0));
|
||||
|
||||
Paint markerPaint = new Paint();
|
||||
markerPaint = new Paint();
|
||||
markerPaint.setStyle(Style.STROKE);
|
||||
markerPaint.setColor(context.getResources().getColor(android.R.color.darker_gray));
|
||||
markerPaint.setAntiAlias(false);
|
||||
@@ -263,12 +270,9 @@ public class ChartView extends View {
|
||||
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||
|
||||
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
||||
markerWidth = statisticsMarker.getIntrinsicWidth();
|
||||
markerHeight = statisticsMarker.getIntrinsicHeight();
|
||||
statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin);
|
||||
waypointMarker = MarkerUtils.getDefaultPhoto(context);
|
||||
markerWidth = waypointMarker.getIntrinsicWidth();
|
||||
markerHeight = waypointMarker.getIntrinsicHeight();
|
||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
scroller = new Scroller(context);
|
||||
@@ -323,11 +327,10 @@ public class ChartView extends View {
|
||||
*
|
||||
* @param dataPoints an array of data points to be added
|
||||
*/
|
||||
public void addDataPoints(ArrayList<double[]> dataPoints) {
|
||||
public void addDataPoints(List<double[]> dataPoints) {
|
||||
synchronized (chartData) {
|
||||
chartData.addAll(dataPoints);
|
||||
for (int i = 0; i < dataPoints.size(); i++) {
|
||||
double[] dataPoint = dataPoints.get(i);
|
||||
for (double[] dataPoint : dataPoints) {
|
||||
xExtremityMonitor.update(dataPoint[0]);
|
||||
for (int j = 0; j < series.length; j++) {
|
||||
if (!Double.isNaN(dataPoint[j + 1])) {
|
||||
@@ -492,6 +495,7 @@ public class ChartView extends View {
|
||||
|
||||
clipToGraphArea(canvas);
|
||||
drawDataSeries(canvas);
|
||||
drawWaypoints(canvas);
|
||||
drawGrid(canvas);
|
||||
|
||||
canvas.restore();
|
||||
@@ -532,6 +536,33 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the waypoints.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawWaypoints(Canvas canvas) {
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
final Waypoint waypoint = waypoints.get(i);
|
||||
if (waypoint.getLocation() == null) {
|
||||
continue;
|
||||
}
|
||||
double xValue = getWaypointXValue(waypoint);
|
||||
if (xValue > maxX) {
|
||||
continue;
|
||||
}
|
||||
canvas.save();
|
||||
float x = getX(getWaypointXValue(waypoint));
|
||||
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
|
||||
canvas.translate(x - (float) (markerWidth * WAYPOINT_X_ANCHOR), topBorder + spacer);
|
||||
|
||||
waypointMarker.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the grid.
|
||||
*
|
||||
@@ -539,9 +570,9 @@ public class ChartView extends View {
|
||||
*/
|
||||
private void drawGrid(Canvas canvas) {
|
||||
// X axis grid
|
||||
ArrayList<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
|
||||
for (int i = 0; i < xAxisMarkerPositions.size(); i++) {
|
||||
int x = getX(xAxisMarkerPositions.get(i));
|
||||
List<Double> xAxisMarkerPositions = getXAxisMarkerPositions(getXAxisInterval());
|
||||
for (double position : xAxisMarkerPositions) {
|
||||
int x = getX(position);
|
||||
canvas.drawLine(x, topBorder, x, topBorder + effectiveHeight, gridPaint);
|
||||
}
|
||||
// Y axis grid
|
||||
@@ -613,10 +644,10 @@ public class ChartView extends View {
|
||||
canvas.drawText(label, x + effectiveWidth + spacer, y + yOffset, axisPaint);
|
||||
|
||||
double interval = getXAxisInterval();
|
||||
ArrayList<Double> markerPositions = getXAxisMarkerPositions(interval);
|
||||
NumberFormat numberFormat = interval < 1 ? X_FRACTION_FORMAT : X_NUMBER_FORMAT;
|
||||
for (int i = 0; i < markerPositions.size(); i++) {
|
||||
drawXAxisMarker(canvas, markerPositions.get(i), numberFormat, spacer + yOffset);
|
||||
|
||||
for (double markerPosition : getXAxisMarkerPositions(interval)) {
|
||||
drawXAxisMarker(canvas, markerPosition, numberFormat, spacer + yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,8 +697,8 @@ public class ChartView extends View {
|
||||
/**
|
||||
* Gets the x axis marker positions.
|
||||
*/
|
||||
private ArrayList<Double> getXAxisMarkerPositions(double interval) {
|
||||
ArrayList<Double> markers = new ArrayList<>();
|
||||
private List<Double> getXAxisMarkerPositions(double interval) {
|
||||
List<Double> markers = new ArrayList<>();
|
||||
markers.add(0d);
|
||||
for (int i = 1; i * interval < maxX; i++) {
|
||||
markers.add(i * interval);
|
||||
@@ -777,8 +808,7 @@ public class ChartView extends View {
|
||||
private void drawPaths() {
|
||||
boolean[] hasMoved = new boolean[series.length];
|
||||
|
||||
for (int i = 0; i < chartData.size(); i++) {
|
||||
double[] dataPoint = chartData.get(i);
|
||||
for (double[] dataPoint : chartData) {
|
||||
for (int j = 0; j < series.length; j++) {
|
||||
double value = dataPoint[j + 1];
|
||||
if (Double.isNaN(value)) {
|
||||
|
||||
@@ -40,17 +40,24 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
*/
|
||||
public class TrackDataHub implements DataSourceListener {
|
||||
|
||||
public synchronized static TrackDataHub newInstance(Context context) {
|
||||
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Target number of track points displayed by the map overlay.
|
||||
* We may display more than this number of points.
|
||||
*/
|
||||
@Deprecated
|
||||
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
|
||||
|
||||
/**
|
||||
* Maximum number of waypoints to displayed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Deprecated
|
||||
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
|
||||
|
||||
private static final String TAG = TrackDataHub.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
@@ -95,10 +102,6 @@ public class TrackDataHub implements DataSourceListener {
|
||||
resetSamplingState();
|
||||
}
|
||||
|
||||
public synchronized static TrackDataHub newInstance(Context context) {
|
||||
return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (started) {
|
||||
Log.i(TAG, "TrackDataHub already started, ignoring start.");
|
||||
@@ -396,8 +399,7 @@ public class TrackDataHub implements DataSourceListener {
|
||||
trackDataListener.clearWaypoints();
|
||||
}
|
||||
|
||||
try (Cursor cursor = contentProviderUtils.getWaypointCursor(
|
||||
selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
|
||||
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
|
||||
@@ -466,12 +468,8 @@ public class TrackDataHub implements DataSourceListener {
|
||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
// TODO: also include the last valid point before a split
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSegmentSplit(location);
|
||||
includeNextPoint = true;
|
||||
}
|
||||
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
|
||||
includeNextPoint = true;
|
||||
} else {
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
|
||||
@@ -51,14 +51,9 @@ public interface TrackDataListener {
|
||||
*/
|
||||
void onSampledOutTrackPoint(Location location);
|
||||
|
||||
/**
|
||||
* Called when an invalid track point representing a segment split is read.
|
||||
*/
|
||||
void onSegmentSplit(Location location);
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)}, {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit(Location)}.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
|
||||
*/
|
||||
void onNewTrackPointsDone();
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ public final class Waypoint {
|
||||
}
|
||||
|
||||
public enum WaypointType {
|
||||
WAYPOINT, STATISTICS
|
||||
WAYPOINT,
|
||||
STATISTICS //Always the first waypoint; only one can exist.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ public class WaypointCreationRequest {
|
||||
|
||||
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(WaypointType.WAYPOINT, false);
|
||||
|
||||
public final static WaypointCreationRequest DEFAULT_STATISTICS = new WaypointCreationRequest(WaypointType.STATISTICS, false);
|
||||
|
||||
@Deprecated // TODO Do not create per
|
||||
public final static WaypointCreationRequest DEFAULT_START_TRACK = new WaypointCreationRequest(WaypointType.STATISTICS, true);
|
||||
|
||||
private WaypointType type;
|
||||
|
||||
@@ -5,8 +5,8 @@ public final class SensorDataSet {
|
||||
public static final float DATA_UNAVAILABLE = Float.NaN;
|
||||
|
||||
//TODO It might be necessary to consider: if sensor was connected as well.
|
||||
public String sensorName;
|
||||
public String sensorAddress;
|
||||
private String sensorName;
|
||||
private String sensorAddress;
|
||||
private float heartRate;
|
||||
private float cadence;
|
||||
private float power;
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
@@ -58,7 +59,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
|
||||
private static final String STATE_CHART_VIEW_BY_DISTANCE_KEY = "chartViewByDistance";
|
||||
|
||||
private final ArrayList<double[]> pendingPoints = new ArrayList<>();
|
||||
private final List<double[]> pendingPoints = new ArrayList<>();
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
@@ -103,7 +104,7 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
||||
|
||||
// Create a chartView here to store data thus won't need to reload all the data on every onStart or onResume.
|
||||
chartView = new ChartView(getActivity(), chartByDistance);
|
||||
chartView = new ChartView(getContext(), chartByDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -198,13 +199,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit(Location location) {
|
||||
if (isResumed()) {
|
||||
fillDataPoint(location, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
@@ -264,9 +258,8 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
}
|
||||
reportSpeed = speed;
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
|
||||
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
|
||||
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
|
||||
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
|
||||
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -303,26 +296,14 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
||||
*/
|
||||
private void checkChartSettings() {
|
||||
boolean needUpdate = false;
|
||||
if (setSeriesEnabled(ChartView.ELEVATION_SERIES, PreferencesUtils.shouldChartShowElevation(getActivity()))) {
|
||||
|
||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
boolean chartShowSpeed = PreferencesUtils.shouldChartShowSpeed(getActivity());
|
||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.POWER_SERIES, PreferencesUtils.shouldChartShowPower(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.CADENCE_SERIES, PreferencesUtils.shouldChartShowCadence(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.HEART_RATE_SERIES, PreferencesUtils.shouldChartShowHeartRate(getActivity()))) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (needUpdate) {
|
||||
chartView.postInvalidate();
|
||||
}
|
||||
|
||||
@@ -41,12 +41,10 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import de.dennisguse.opentracks.MarkerEditActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.StatsUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -62,7 +60,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private Handler handler;
|
||||
private ImageView photo;
|
||||
private ImageView photoView;
|
||||
private ImageView textGradient;
|
||||
private LinearLayout waypointInfo;
|
||||
private Waypoint waypoint;
|
||||
@@ -119,11 +117,11 @@ public class MarkerDetailFragment extends Fragment {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
|
||||
|
||||
photo = view.findViewById(R.id.marker_detail_waypoint_photo);
|
||||
photoView = view.findViewById(R.id.marker_detail_waypoint_photo);
|
||||
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||
|
||||
photo.setOnClickListener(new View.OnClickListener() {
|
||||
photoView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handler.removeCallbacks(hideText);
|
||||
@@ -156,11 +154,8 @@ public class MarkerDetailFragment extends Fragment {
|
||||
@Override
|
||||
public void setMenuVisibility(boolean menuVisible) {
|
||||
super.setMenuVisibility(menuVisible);
|
||||
/*
|
||||
* View pager caches the neighboring fragments in the resumed state. If
|
||||
* becoming visible from the resumed state, update the UI to display the
|
||||
* text above the image.
|
||||
*/
|
||||
// View pager caches the neighboring fragments in the resumed state.
|
||||
// If becoming visible from the resumed state, update the UI to display the text above the image.
|
||||
if (isResumed()) {
|
||||
if (menuVisible) {
|
||||
updateUi();
|
||||
@@ -214,47 +209,25 @@ public class MarkerDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI.
|
||||
*/
|
||||
private void updateUi() {
|
||||
View waypointView = getView().findViewById(R.id.marker_detail_waypoint);
|
||||
View statisticsView = getView().findViewById(R.id.marker_detail_statistics);
|
||||
|
||||
boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT;
|
||||
waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE);
|
||||
statisticsView.setVisibility(isWaypoint ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (isWaypoint) {
|
||||
boolean hasPhoto = waypoint.hasPhoto();
|
||||
photo.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||
waypointInfo.setVisibility(View.VISIBLE);
|
||||
|
||||
if (hasPhoto) {
|
||||
handler.removeCallbacks(hideText);
|
||||
photo.setImageURI(waypoint.getPhotoURI());
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
}
|
||||
|
||||
setName(R.id.marker_detail_waypoint_name, hasPhoto);
|
||||
|
||||
TextView category = getView().findViewById(R.id.marker_detail_waypoint_category);
|
||||
ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto);
|
||||
|
||||
TextView description = getView().findViewById(R.id.marker_detail_waypoint_description);
|
||||
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
|
||||
|
||||
setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
|
||||
boolean hasPhoto = waypoint.hasPhoto();
|
||||
if (hasPhoto) {
|
||||
handler.removeCallbacks(hideText);
|
||||
photoView.setImageURI(waypoint.getPhotoURI());
|
||||
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||
} else {
|
||||
setName(R.id.marker_detail_statistics_name, false);
|
||||
|
||||
setLocation(R.id.marker_detail_statistics_location, false);
|
||||
|
||||
Track track = contentProviderUtils.getTrack(waypoint.getTrackId());
|
||||
StatsUtils.setTripStatisticsValues(getActivity(), waypoint.getTripStatistics(), null);
|
||||
StatsUtils.setLocationValues(getActivity(), waypoint.getLocation(), false);
|
||||
photoView.setImageResource(MarkerUtils.ICON_ID);
|
||||
}
|
||||
|
||||
setName(R.id.marker_detail_waypoint_name, hasPhoto);
|
||||
|
||||
TextView category = getView().findViewById(R.id.marker_detail_waypoint_category);
|
||||
ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto);
|
||||
|
||||
TextView description = getView().findViewById(R.id.marker_detail_waypoint_description);
|
||||
ListItemUtils.setTextView(getActivity(), description, waypoint.getDescription(), hasPhoto);
|
||||
|
||||
setLocation(R.id.marker_detail_waypoint_location, hasPhoto);
|
||||
}
|
||||
|
||||
private void setName(int resId, boolean addShadow) {
|
||||
|
||||
@@ -20,16 +20,18 @@ import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -43,9 +45,11 @@ import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.StatsUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -59,26 +63,17 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
||||
|
||||
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
private Handler handlerUpdateUI;
|
||||
|
||||
//TODO Initialize immediately and remove in onDestroy()
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private Location lastLocation = null;
|
||||
private TripStatistics lastTripStatistics = null;
|
||||
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
||||
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
|
||||
updateSensorDataUI();
|
||||
}
|
||||
|
||||
handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND);
|
||||
}
|
||||
}
|
||||
};
|
||||
private String category = "";
|
||||
private int recordingGpsAccuracy;
|
||||
|
||||
@@ -87,6 +82,111 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
return inflater.inflate(R.layout.stats, container, false);
|
||||
}
|
||||
|
||||
/* Views */
|
||||
private View sensorContainerView;
|
||||
private TextView heartRateValueView;
|
||||
private TextView heartRateSensorView;
|
||||
|
||||
private TextView totalTimeValueView;
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
||||
updateTotalTime();
|
||||
updateSensorDataUI();
|
||||
}
|
||||
|
||||
handlerUpdateUI.postDelayed(this, UI_UPDATE_INTERVAL);
|
||||
}
|
||||
}
|
||||
};
|
||||
private TextView distanceValue;
|
||||
private TextView distanceUnit;
|
||||
private View activityLabel;
|
||||
private Spinner activitySpinner;
|
||||
private TextView movingTimeValue;
|
||||
private TextView speedAvgLabel;
|
||||
private TextView speedAvgValue;
|
||||
private TextView speedAvgUnit;
|
||||
private TextView speedMaxLabel;
|
||||
private TextView speedMaxValue;
|
||||
private TextView speedMaxUnit;
|
||||
private TextView speedMovingLabel;
|
||||
private TextView speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
private View gradeElevationSeparator;
|
||||
private View gradeElevationContainer;
|
||||
private TextView elevationMinValue;
|
||||
private TextView elevationMinUnit;
|
||||
private TextView elevationMaxValue;
|
||||
private TextView elevationMaxUnit;
|
||||
private View speedContainer;
|
||||
private TextView speedLabel;
|
||||
private TextView speedValue;
|
||||
private TextView speedUnit;
|
||||
private View elevationContainer;
|
||||
private TextView elevationValue;
|
||||
private TextView elevationUnit;
|
||||
private View coordinateSeparator;
|
||||
private View coordinateContainer;
|
||||
private TextView latitudeValue;
|
||||
private TextView longitudeValue;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
sensorContainerView = view.findViewById(R.id.stats_sensor_container);
|
||||
heartRateValueView = view.findViewById(R.id.stats_sensor_heart_rate_value);
|
||||
heartRateSensorView = view.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||
|
||||
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
|
||||
|
||||
distanceValue = view.findViewById(R.id.stats_distance_value);
|
||||
distanceUnit = view.findViewById(R.id.stats_distance_unit);
|
||||
|
||||
activityLabel = view.findViewById(R.id.stats_activity_type_label);
|
||||
activitySpinner = view.findViewById(R.id.stats_activity_type_icon);
|
||||
|
||||
movingTimeValue = view.findViewById(R.id.stats_moving_time_value);
|
||||
|
||||
speedAvgLabel = view.findViewById(R.id.stats_average_speed_label);
|
||||
speedAvgValue = view.findViewById(R.id.stats_average_speed_value);
|
||||
speedAvgUnit = view.findViewById(R.id.stats_average_speed_unit);
|
||||
|
||||
speedMaxLabel = view.findViewById(R.id.stats_max_speed_label);
|
||||
speedMaxValue = view.findViewById(R.id.stats_max_speed_value);
|
||||
speedMaxUnit = view.findViewById(R.id.stats_max_speed_unit);
|
||||
|
||||
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||
|
||||
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||
|
||||
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
|
||||
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
|
||||
|
||||
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
|
||||
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
|
||||
|
||||
speedContainer = view.findViewById(R.id.stats_speed);
|
||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||
|
||||
elevationContainer = view.findViewById(R.id.stats_elevation);
|
||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||
|
||||
coordinateSeparator = view.findViewById(R.id.stats_coordinate_separator);
|
||||
coordinateContainer = view.findViewById(R.id.stats_coordinate_container);
|
||||
|
||||
latitudeValue = view.findViewById(R.id.stats_latitude_value);
|
||||
longitudeValue = view.findViewById(R.id.stats_longitude_value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@@ -125,10 +225,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
updateUi(getActivity());
|
||||
if (isSelectedTrackRecording()) {
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
}
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,10 +238,66 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
if (trackRecordingServiceConnection != null) {
|
||||
trackRecordingServiceConnection.unbind();
|
||||
}
|
||||
trackRecordingServiceConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
sensorContainerView = null;
|
||||
heartRateValueView = null;
|
||||
heartRateSensorView = null;
|
||||
|
||||
totalTimeValueView = null;
|
||||
|
||||
distanceValue = null;
|
||||
distanceUnit = null;
|
||||
|
||||
activityLabel = null;
|
||||
activitySpinner = null;
|
||||
|
||||
movingTimeValue = null;
|
||||
|
||||
speedAvgLabel = null;
|
||||
speedAvgValue = null;
|
||||
speedAvgUnit = null;
|
||||
|
||||
speedMaxLabel = null;
|
||||
speedMaxValue = null;
|
||||
speedMaxUnit = null;
|
||||
|
||||
speedMovingLabel = null;
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
gradeElevationSeparator = null;
|
||||
gradeElevationContainer = null;
|
||||
|
||||
elevationMinValue = null;
|
||||
elevationMinUnit = null;
|
||||
|
||||
elevationMaxValue = null;
|
||||
elevationMaxUnit = null;
|
||||
|
||||
speedContainer = null;
|
||||
speedLabel = null;
|
||||
speedValue = null;
|
||||
speedUnit = null;
|
||||
|
||||
elevationContainer = null;
|
||||
elevationValue = null;
|
||||
elevationUnit = null;
|
||||
|
||||
coordinateSeparator = null;
|
||||
coordinateContainer = null;
|
||||
|
||||
latitudeValue = null;
|
||||
longitudeValue = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
if (isResumed()) {
|
||||
@@ -154,7 +307,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
if (isResumed()) {
|
||||
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUi(getActivity());
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -176,11 +329,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
lastLocation = location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSegmentSplit(Location location) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
@@ -200,7 +348,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
lastLocation = null;
|
||||
}
|
||||
}
|
||||
StatsUtils.setLocationValues(getActivity(), lastLocation, isSelectedTrackRecording());
|
||||
setLocationValues();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -229,7 +377,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
updateUi(getActivity());
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -244,7 +392,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
updateUi(getActivity());
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -300,6 +448,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
updateStats();
|
||||
setLocationValues();
|
||||
updateSensorDataUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
|
||||
*/
|
||||
@@ -314,16 +468,176 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
}
|
||||
|
||||
StatsUtils.setSensorData(getActivity(), sensorDataSet, isSelectedTrackRecording());
|
||||
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI.
|
||||
*/
|
||||
private void updateUi(FragmentActivity activity) {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
|
||||
StatsUtils.setTripStatisticsValues(activity, lastTripStatistics, trackIconValue);
|
||||
StatsUtils.setLocationValues(activity, lastLocation, isSelectedTrackRecording());
|
||||
updateSensorDataUI();
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
// heart rate
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext())) {
|
||||
isVisible = View.INVISIBLE;
|
||||
}
|
||||
sensorContainerView.setVisibility(isVisible);
|
||||
|
||||
if (isRecording) {
|
||||
String heartRate = getContext().getString(R.string.value_unknown);
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
|
||||
sensorName = sensorDataSet.getSensorName();
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
heartRateSensorView.setText(sensorName);
|
||||
heartRateValueView.setText(heartRate);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
|
||||
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||
boolean isRecording = isSelectedTrackRecording();
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||
|
||||
distanceValue.setText(parts.first);
|
||||
distanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
activityLabel.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||
|
||||
activitySpinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||
activitySpinner.setEnabled(isRecording);
|
||||
if (trackIconValue != null) {
|
||||
TrackIconUtils.setIconSpinner(activitySpinner, trackIconValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Set time
|
||||
if (lastTripStatistics != null) {
|
||||
movingTimeValue.setText(StringUtils.formatElapsedTime(lastTripStatistics.getMovingTime()));
|
||||
updateTotalTime();
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageSpeed() : Double.NaN;
|
||||
speedAvgLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
speedAvgValue.setText(parts.first);
|
||||
speedAvgUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxSpeed();
|
||||
|
||||
speedMaxLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
speedMaxValue.setText(parts.first);
|
||||
speedMaxUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
|
||||
speedMovingLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
speedMovingValue.setText(parts.first);
|
||||
speedMovingUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
|
||||
// Set elevation
|
||||
{
|
||||
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
{
|
||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||
|
||||
elevationMinValue.setText(parts.first);
|
||||
elevationMinUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
{
|
||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||
|
||||
elevationMaxValue.setText(parts.first);
|
||||
elevationMaxUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
long totalTime;
|
||||
if (isSelectedTrackRecording()) {
|
||||
totalTime = System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime();
|
||||
} else {
|
||||
totalTime = lastTripStatistics.getTotalTime();
|
||||
}
|
||||
totalTimeValueView.setText(StringUtils.formatElapsedTime(totalTime));
|
||||
}
|
||||
|
||||
private void setLocationValues() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||
boolean isRecording = isSelectedTrackRecording();
|
||||
|
||||
// Set speed/pace
|
||||
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
double speed = lastLocation != null && lastLocation.hasSpeed() ? lastLocation.getSpeed() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
speedValue.setText(parts.first);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showGradeElevation) {
|
||||
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||
|
||||
elevationValue.setText(parts.first);
|
||||
elevationUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(getContext());
|
||||
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
double latitude = lastLocation != null ? lastLocation.getLatitude() : Double.NaN;
|
||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||
latitudeValue.setText(latitudeText);
|
||||
|
||||
double longitude = lastLocation != null ? lastLocation.getLongitude() : Double.NaN;
|
||||
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
|
||||
longitudeValue.setText(longitudeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ public enum TrackFileFormat {
|
||||
}
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, trackExporterListener, exportPhotos);
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
@@ -188,7 +188,7 @@ public enum TrackFileFormat {
|
||||
|
||||
public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1);
|
||||
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
|
||||
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,16 +103,8 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
||||
}
|
||||
|
||||
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||
|
||||
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
|
||||
// Object retained = getLastNonConfigurationInstance();
|
||||
// if (retained instanceof ExportAsyncTask) {
|
||||
// exportAsyncTask = (ExportAsyncTask) retained;
|
||||
// exportAsyncTask.setActivity(this);
|
||||
// } else {
|
||||
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
|
||||
exportAsyncTask.execute();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,19 +44,16 @@ import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.DescriptionGeneratorImpl;
|
||||
import de.dennisguse.opentracks.content.LocationFactory;
|
||||
import de.dennisguse.opentracks.content.LocationIterator;
|
||||
import de.dennisguse.opentracks.content.SensorDataSetLocation;
|
||||
import de.dennisguse.opentracks.content.Track;
|
||||
import de.dennisguse.opentracks.content.Waypoint;
|
||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
import de.dennisguse.opentracks.services.tasks.SplitPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
@@ -75,8 +72,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*/
|
||||
public class TrackRecordingService extends Service {
|
||||
|
||||
// The name of extra intent property to indicate whether we want to resume a previously recorded track.
|
||||
public static final String RESUME_TRACK_EXTRA_NAME = "RESUME_TRACK";
|
||||
private static final int NOTIFICATION_ID = 123;
|
||||
|
||||
public static final double PAUSE_LATITUDE = 100.0;
|
||||
@@ -84,8 +79,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
@VisibleForTesting
|
||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
// 1 minute in milliseconds
|
||||
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||
@@ -95,7 +88,6 @@ public class TrackRecordingService extends Service {
|
||||
private Handler handler;
|
||||
private LocationManagerConnector locationManagerConnector;
|
||||
private PeriodicTaskExecutor voiceExecutor;
|
||||
private PeriodicTaskExecutor splitExecutor;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingServiceNotificationManager notificationManager;
|
||||
private long recordingTrackId;
|
||||
@@ -104,7 +96,6 @@ public class TrackRecordingService extends Service {
|
||||
private int recordingDistanceInterval;
|
||||
private int maxRecordingDistance;
|
||||
private int recordingGpsAccuracy;
|
||||
private int autoResumeTrackTimeout;
|
||||
private long currentRecordingInterval;
|
||||
|
||||
// The following variables are set when recording:
|
||||
@@ -126,14 +117,10 @@ public class TrackRecordingService extends Service {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
voiceExecutor.setMetricUnits(metricUnits);
|
||||
splitExecutor.setMetricUnits(metricUnits);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.split_frequency_key, key)) {
|
||||
splitExecutor.setTaskFrequency(PreferencesUtils.getSplitFrequency(context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||
@@ -155,12 +142,9 @@ public class TrackRecordingService extends Service {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_gps_accuracy_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.auto_resume_track_timeout_key, key)) {
|
||||
autoResumeTrackTimeout = PreferencesUtils.getAutoResumeTrackTimeout(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
private TripStatisticsUpdater markerTripStatisticsUpdater;
|
||||
|
||||
private WakeLock wakeLock;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
private Location lastLocation;
|
||||
@@ -219,7 +203,6 @@ public class TrackRecordingService extends Service {
|
||||
handler = new Handler();
|
||||
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -247,7 +230,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
handleStartCommand(intent, startId);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@@ -272,12 +254,6 @@ public class TrackRecordingService extends Service {
|
||||
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
try {
|
||||
splitExecutor.shutdown();
|
||||
} finally {
|
||||
splitExecutor = null;
|
||||
}
|
||||
|
||||
try {
|
||||
voiceExecutor.shutdown();
|
||||
} finally {
|
||||
@@ -298,23 +274,14 @@ public class TrackRecordingService extends Service {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the service is recording.
|
||||
*/
|
||||
public boolean isRecording() {
|
||||
return PreferencesUtils.isRecording(recordingTrackId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current recording is paused.
|
||||
*/
|
||||
public boolean isPaused() {
|
||||
return recordingTrackPaused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trip statistics.
|
||||
*/
|
||||
public TripStatistics getTripStatistics() {
|
||||
if (trackTripStatisticsUpdater == null) {
|
||||
return null;
|
||||
@@ -333,43 +300,19 @@ public class TrackRecordingService extends Service {
|
||||
return -1L;
|
||||
}
|
||||
|
||||
WaypointType waypointType = waypointCreationRequest.getType();
|
||||
boolean isStatistics = waypointType == WaypointType.STATISTICS;
|
||||
|
||||
// Get name
|
||||
String name;
|
||||
if (waypointCreationRequest.getName() != null) {
|
||||
name = waypointCreationRequest.getName();
|
||||
} else {
|
||||
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointType);
|
||||
String name = waypointCreationRequest.getName();
|
||||
if (name == null) {
|
||||
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId, waypointCreationRequest.getType());
|
||||
if (nextWaypointNumber == -1) {
|
||||
nextWaypointNumber = 0;
|
||||
}
|
||||
name = getString(isStatistics ? R.string.marker_split_name_format : R.string.marker_name_format, nextWaypointNumber);
|
||||
name = getString(R.string.marker_name_format, nextWaypointNumber);
|
||||
}
|
||||
|
||||
// Get category
|
||||
String category = waypointCreationRequest.getCategory() != null ? waypointCreationRequest.getCategory() : "";
|
||||
String description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
|
||||
String icon = getString(R.string.marker_waypoint_icon_url);
|
||||
|
||||
// Get tripStatistics, description, and icon
|
||||
TripStatistics tripStatistics;
|
||||
String description;
|
||||
String icon;
|
||||
if (isStatistics) {
|
||||
long now = System.currentTimeMillis();
|
||||
markerTripStatisticsUpdater.updateTime(now);
|
||||
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
|
||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||
description = new DescriptionGeneratorImpl(this).generateWaypointDescription(tripStatistics);
|
||||
icon = getString(R.string.marker_statistics_icon_url);
|
||||
} else {
|
||||
tripStatistics = null;
|
||||
description = waypointCreationRequest.getDescription() != null ? waypointCreationRequest.getDescription() : "";
|
||||
//TODO Bundle icon?
|
||||
icon = getString(R.string.marker_waypoint_icon_url);
|
||||
}
|
||||
|
||||
// Get length and duration
|
||||
double length;
|
||||
long duration;
|
||||
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
|
||||
@@ -392,63 +335,11 @@ public class TrackRecordingService extends Service {
|
||||
String photoUrl = waypointCreationRequest.getPhotoUrl() != null ? waypointCreationRequest.getPhotoUrl() : "";
|
||||
|
||||
// Insert waypoint
|
||||
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointType, length, duration, -1L, -1L, location, tripStatistics, photoUrl);
|
||||
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, waypointCreationRequest.getType(), length, duration, -1L, -1L, location, null, photoUrl);
|
||||
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
|
||||
return Long.parseLong(uri.getLastPathSegment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles start command.
|
||||
*
|
||||
* @param intent the intent
|
||||
* @param startId the start id
|
||||
*/
|
||||
private void handleStartCommand(Intent intent, int startId) {
|
||||
// Check if the service is called to resume track (from phone reboot)
|
||||
boolean resumeTrackRequested = intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false);
|
||||
if (resumeTrackRequested && !shouldResumeTrack()) {
|
||||
Log.i(TAG, "Stop resume track.");
|
||||
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
|
||||
stopSelfResult(startId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if should resume.
|
||||
*/
|
||||
private boolean shouldResumeTrack() {
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
|
||||
if (track == null) {
|
||||
Log.d(TAG, "Not resuming. Track is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int retries = PreferencesUtils.getAutoResumeTrackCurrentRetryDefault(this);
|
||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
||||
Log.d(TAG, "Not resuming. Exceeded maximum retry attempts.");
|
||||
return false;
|
||||
}
|
||||
PreferencesUtils.incrementAutoResumeTrackCurrentRetryDefault(this);
|
||||
|
||||
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||
Log.d(TAG, "Not resuming. Auto-resume track timeout set to never.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (autoResumeTrackTimeout == Integer.parseInt(getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||
Log.d(TAG, "Resuming. Auto-resume track timeout set to always.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (track.getTripStatistics() == null) {
|
||||
Log.d(TAG, "Not resuming. No trip statistics.");
|
||||
return false;
|
||||
}
|
||||
long stopTime = track.getTripStatistics().getStopTime();
|
||||
return stopTime > 0 && (System.currentTimeMillis() - stopTime) <= autoResumeTrackTimeout * ONE_MINUTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new track.
|
||||
*
|
||||
@@ -461,7 +352,6 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||
|
||||
// Insert a track
|
||||
Track track = new Track();
|
||||
@@ -470,7 +360,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Update shared preferences
|
||||
updateRecordingState(trackId, false);
|
||||
PreferencesUtils.resetAutoResumeTrackCurrentRetryDefault(this);
|
||||
|
||||
// Update database
|
||||
track.setId(trackId);
|
||||
@@ -481,6 +370,8 @@ public class TrackRecordingService extends Service {
|
||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
//TODO Do not use insertWaypoint
|
||||
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
|
||||
|
||||
startRecording(true);
|
||||
@@ -498,23 +389,11 @@ public class TrackRecordingService extends Service {
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
|
||||
|
||||
long markerStartTime;
|
||||
Waypoint waypoint = contentProviderUtils.getLastWaypoint(recordingTrackId, WaypointType.STATISTICS);
|
||||
if (waypoint != null && waypoint.getTripStatistics() != null) {
|
||||
markerStartTime = waypoint.getTripStatistics().getStopTime();
|
||||
} else {
|
||||
markerStartTime = tripStatistics.getStartTime();
|
||||
}
|
||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
|
||||
|
||||
try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
|
||||
while (locationIterator.hasNext()) {
|
||||
Location location = locationIterator.next();
|
||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
if (location.getTime() > markerStartTime) {
|
||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "RuntimeException", e);
|
||||
@@ -549,8 +428,7 @@ public class TrackRecordingService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for starting a new track, resuming a track, or restarting after
|
||||
* phone reboot.
|
||||
* Common code for starting a new track, resuming a track, or restarting after phone reboot.
|
||||
*
|
||||
* @param trackStarted true if track is started, false if track is resumed
|
||||
*/
|
||||
@@ -566,7 +444,6 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
// Restore periodic tasks
|
||||
voiceExecutor.restore();
|
||||
splitExecutor.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -611,7 +488,7 @@ public class TrackRecordingService extends Service {
|
||||
contentProviderUtils.updateTrack(track);
|
||||
}
|
||||
}
|
||||
endRecording(true, trackId);
|
||||
endRecording(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,7 +516,7 @@ public class TrackRecordingService extends Service {
|
||||
insertLocation(track, pause, null);
|
||||
}
|
||||
|
||||
endRecording(false, recordingTrackId);
|
||||
endRecording(false);
|
||||
|
||||
notificationManager.updateContent(getString(R.string.generic_paused));
|
||||
}
|
||||
@@ -648,12 +525,10 @@ public class TrackRecordingService extends Service {
|
||||
* Common code for ending a track or pausing a track.
|
||||
*
|
||||
* @param trackStopped true if track is stopped, false if track is paused
|
||||
* @param trackId the track id
|
||||
*/
|
||||
private void endRecording(boolean trackStopped, long trackId) {
|
||||
private void endRecording(boolean trackStopped) {
|
||||
// Shutdown periodic tasks
|
||||
voiceExecutor.shutdown();
|
||||
splitExecutor.shutdown();
|
||||
|
||||
// Update instance variables
|
||||
if (remoteSensorManager != null) {
|
||||
@@ -830,7 +705,6 @@ public class TrackRecordingService extends Service {
|
||||
Uri uri = contentProviderUtils.insertTrackPoint(location, track.getId());
|
||||
long trackPointId = Long.parseLong(uri.getLastPathSegment());
|
||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
|
||||
} catch (SQLiteException e) {
|
||||
/*
|
||||
@@ -840,7 +714,6 @@ public class TrackRecordingService extends Service {
|
||||
Log.w(TAG, "SQLiteException", e);
|
||||
}
|
||||
voiceExecutor.update();
|
||||
splitExecutor.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1031,15 +904,7 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
@Override
|
||||
public SensorDataSet getSensorData() {
|
||||
if (trackRecordingService.remoteSensorManager == null) {
|
||||
Log.d(TAG, "remoteSensorManager is null.");
|
||||
return null;
|
||||
}
|
||||
if (trackRecordingService.getSensorDataSet() == null) {
|
||||
Log.d(TAG, "Sensor data set is null.");
|
||||
return null;
|
||||
}
|
||||
return trackRecordingService.remoteSensorManager.getSensorDataSet();
|
||||
return trackRecordingService.getSensorDataSet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,6 +48,8 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
|
||||
private static final String TAG = TrackRecordingServiceConnection.class.getSimpleName();
|
||||
|
||||
@Deprecated
|
||||
//TODO Always the context of the owner of a service connection (i.e., let it be passed to every call).
|
||||
private final Context context;
|
||||
private final Runnable callback;
|
||||
|
||||
|
||||
@@ -86,11 +86,11 @@ public class BluetoothRemoteSensorManager {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
|
||||
if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||
if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
|
||||
return;
|
||||
}
|
||||
@@ -145,12 +145,11 @@ public class BluetoothRemoteSensorManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
|
||||
if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
|
||||
Log.w(TAG, "No bluetooth address.");
|
||||
return;
|
||||
}
|
||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||
|
||||
BluetoothDevice device;
|
||||
|
||||
@@ -21,12 +21,8 @@ import android.media.AudioManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -34,7 +30,6 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TTSUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
@@ -47,22 +42,54 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
/**
|
||||
* The rate at which announcements are spoken.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final float TTS_SPEECH_RATE = 0.9f;
|
||||
private static final float TTS_SPEECH_RATE = 0.9f;
|
||||
|
||||
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
Log.d(TAG, "Audio focus changed to " + focusChange);
|
||||
|
||||
boolean stop = false;
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_GAIN:
|
||||
stop = false;
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
stop = true;
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
stop = true;
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (stop && tts != null && tts.isSpeaking()) {
|
||||
tts.stop();
|
||||
Log.i(TAG, "Aborting current tts due to focus change " + focusChange);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
|
||||
@Override
|
||||
public void onStart(String utteranceId) {
|
||||
int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.w(TAG, "Failed to request audio focus.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(String utteranceId) {
|
||||
int result = audioManager.abandonAudioFocus(null);
|
||||
int result = audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.w(TAG, "Failed to relinquish audio focus.");
|
||||
}
|
||||
@@ -70,32 +97,16 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
@Override
|
||||
public void onError(String utteranceId) {
|
||||
Log.e(TAG, "An error occurred for utteranceId " + utteranceId);
|
||||
}
|
||||
};
|
||||
|
||||
private TextToSpeech tts;
|
||||
// Response from TTS after its initialization
|
||||
private int initStatus = TextToSpeech.ERROR;
|
||||
private int ttsInitStatus = TextToSpeech.ERROR;
|
||||
|
||||
private boolean ttsReady = false;
|
||||
|
||||
// True if speech is allowed
|
||||
private boolean speechAllowed;
|
||||
|
||||
/**
|
||||
* Listener which updates {@link #speechAllowed} when the phone state changes.
|
||||
*/
|
||||
private final PhoneStateListener phoneStateListener = new PhoneStateListener() {
|
||||
@Override
|
||||
public void onCallStateChanged(int state, String incomingNumber) {
|
||||
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
|
||||
if (!speechAllowed && tts != null && tts.isSpeaking()) {
|
||||
// If we're already speaking, stop it.
|
||||
tts.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AnnouncementPeriodicTask(Context context) {
|
||||
this.context = context;
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
@@ -103,16 +114,17 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Log.d(TAG, "Start");
|
||||
|
||||
if (tts == null) {
|
||||
tts = newTextToSpeech(context, new OnInitListener() {
|
||||
tts = new TextToSpeech(context, new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
initStatus = status;
|
||||
Log.i(TAG, "TextToSpeech initialized with status " + status);
|
||||
ttsInitStatus = status;
|
||||
}
|
||||
});
|
||||
}
|
||||
speechAllowed = true;
|
||||
listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +141,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
*
|
||||
* @param tripStatistics the trip statistics
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void announce(TripStatistics tripStatistics) {
|
||||
private void announce(TripStatistics tripStatistics) {
|
||||
if (tripStatistics == null) {
|
||||
Log.e(TAG, "TripStatistics is null.");
|
||||
return;
|
||||
@@ -138,7 +149,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
synchronized (this) {
|
||||
if (!ttsReady) {
|
||||
ttsReady = initStatus == TextToSpeech.SUCCESS;
|
||||
ttsReady = ttsInitStatus == TextToSpeech.SUCCESS;
|
||||
if (ttsReady) {
|
||||
onTtsReady();
|
||||
}
|
||||
@@ -149,7 +160,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
}
|
||||
}
|
||||
|
||||
if (!speechAllowed) {
|
||||
if (audioManager.getMode() == AudioManager.MODE_IN_CALL || audioManager.getMode() == AudioManager.MODE_IN_COMMUNICATION) {
|
||||
Log.i(TAG, "Speech is not allowed at this time.");
|
||||
return;
|
||||
}
|
||||
@@ -158,21 +169,18 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
if (tts != null) {
|
||||
tts.shutdown();
|
||||
tts = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when TTS is ready.
|
||||
*/
|
||||
private void onTtsReady() {
|
||||
Locale locale = Locale.getDefault();
|
||||
int languageAvailability = tts.isLanguageAvailable(locale);
|
||||
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA
|
||||
|| languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
Log.w(TAG, "Default locale not available, use English.");
|
||||
locale = Locale.ENGLISH;
|
||||
/*
|
||||
@@ -188,42 +196,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
tts.setOnUtteranceProgressListener(utteranceListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks the announcement.
|
||||
*
|
||||
* @param announcement the announcement
|
||||
*/
|
||||
private void speakAnnouncement(String announcement) {
|
||||
int result = audioManager.requestAudioFocus(null, TTSUtils.getTTSStream(), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||
Log.w(TAG, "Failed to request audio focus.");
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't care about the utterance id. It is supplied here to force
|
||||
* onUtteranceCompleted to be called.
|
||||
*/
|
||||
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link TextToSpeech}.
|
||||
*
|
||||
* @param aContext a context
|
||||
* @param onInitListener an on init listener
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected TextToSpeech newTextToSpeech(Context aContext, OnInitListener onInitListener) {
|
||||
return new TextToSpeech(aContext, onInitListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the announcement.
|
||||
*
|
||||
* @param tripStatistics the trip statistics
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected String getAnnouncement(TripStatistics tripStatistics) {
|
||||
private String getAnnouncement(TripStatistics tripStatistics) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
@@ -256,27 +234,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
getAnnounceTime(tripStatistics.getMovingTime()), rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to phone state.
|
||||
*
|
||||
* @param listener the listener
|
||||
* @param events the interested events
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
||||
TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if (telephony != null) {
|
||||
telephony.listen(listener, events);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the announce time.
|
||||
*
|
||||
* @param time the time
|
||||
*/
|
||||
@VisibleForTesting
|
||||
String getAnnounceTime(long time) {
|
||||
private String getAnnounceTime(long time) {
|
||||
int[] parts = StringUtils.getTimeParts(time);
|
||||
String seconds = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.services.tasks;
|
||||
|
||||
import de.dennisguse.opentracks.content.WaypointCreationRequest;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
|
||||
/**
|
||||
* A simple task to insert statistics markers periodically.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SplitPeriodicTask implements PeriodicTask {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TrackRecordingService trackRecordingService) {
|
||||
trackRecordingService.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
}
|
||||
@@ -1,32 +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.services.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* A {@link PeriodicTaskFactory} for {@link SplitPeriodicTask}.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class SplitPeriodicTaskFactory implements PeriodicTaskFactory {
|
||||
|
||||
@Override
|
||||
public PeriodicTask create(Context context) {
|
||||
return new SplitPeriodicTask();
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
//TODO Check if there is a better way to achieve identical look and feel to ListPreference.
|
||||
//Use material design single choice; for old style use: android.R.layout.select_dialog_singlechoice
|
||||
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, null);
|
||||
currentView = inflater.inflate(androidx.appcompat.R.layout.select_dialog_singlechoice_material, parent);
|
||||
}
|
||||
|
||||
Device device = devices.get(position);
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Preference to select a discoverable Bluetooth LE device.
|
||||
@@ -33,6 +34,8 @@ public class BluetoothLePreference extends DialogPreference {
|
||||
|
||||
private final static String TAG = BluetoothLePreference.class.getSimpleName();
|
||||
|
||||
private final static int DEVICE_NONE_RESOURCEID = R.string.value_none;
|
||||
|
||||
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@@ -75,6 +78,10 @@ public class BluetoothLePreference extends DialogPreference {
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
if (getValue() == null || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext(), getValue())) {
|
||||
return getContext().getString(DEVICE_NONE_RESOURCEID);
|
||||
}
|
||||
|
||||
return getValue();
|
||||
}
|
||||
|
||||
@@ -142,7 +149,7 @@ public class BluetoothLePreference extends DialogPreference {
|
||||
}
|
||||
|
||||
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
|
||||
listAdapter.add(getContext().getString(R.string.value_none), deviceNone);
|
||||
listAdapter.add(getContext().getString(DEVICE_NONE_RESOURCEID), deviceNone);
|
||||
selectedEntryIndex = 0;
|
||||
|
||||
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
|
||||
|
||||
@@ -142,22 +142,4 @@ final class PreferenceHelper {
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
static String[] getAutoResumeTrackTimeoutEntries(Context context) {
|
||||
String[] entryValues = context.getResources().getStringArray(R.array.auto_resume_track_timeout_values);
|
||||
String[] entries = new String[entryValues.length];
|
||||
|
||||
for (int i = 0; i < entryValues.length; i++) {
|
||||
int value = Integer.parseInt(entryValues[i]);
|
||||
if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_never))) {
|
||||
entries[i] = context.getString(R.string.value_never);
|
||||
} else if (value == Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_always))) {
|
||||
entries[i] = context.getString(R.string.value_always);
|
||||
} else {
|
||||
entries[i] = context.getString(R.string.value_integer_minute, value);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +73,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
}
|
||||
});
|
||||
}
|
||||
if (PreferencesUtils.isKey(getActivity(), R.string.stats_rate_key, key)) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateSpeed();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,7 +96,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
PreferencesUtils.getSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
updateSpeed();
|
||||
updateUnits();
|
||||
updateReset();
|
||||
updateBluetooth();
|
||||
@@ -150,11 +141,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
resetPreference.setEnabled(!isRecording);
|
||||
}
|
||||
|
||||
private void updateSpeed() {
|
||||
Preference speedCheckBoxPreference = findPreference(getString(R.string.chart_show_speed_key));
|
||||
speedCheckBoxPreference.setTitle(PreferencesUtils.isReportSpeed(getActivity()) ? R.string.stats_speed : R.string.stats_pace);
|
||||
}
|
||||
|
||||
private void updateBluetooth() {
|
||||
// Disable Bluetooth preference if device does not have Bluetooth
|
||||
BluetoothLePreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key));
|
||||
@@ -167,9 +153,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
|
||||
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
||||
|
||||
ListPreference splitFrequency = findPreference(getString(R.string.split_frequency_key));
|
||||
splitFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
||||
|
||||
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
|
||||
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
|
||||
|
||||
@@ -182,9 +165,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
||||
ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key));
|
||||
recordingGpsAccuracy.setEntries(PreferenceHelper.getRecordingGpsAccuracyEntries(getActivity(), metricUnits));
|
||||
|
||||
ListPreference autoresumeTrack = findPreference(getString(R.string.auto_resume_track_timeout_key));
|
||||
autoresumeTrack.setEntries(PreferenceHelper.getAutoResumeTrackTimeoutEntries(getActivity()));
|
||||
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class MarkerUtils {
|
||||
|
||||
public static final int ICON_ID = R.drawable.ic_marker_blue_pushpin;
|
||||
|
||||
private MarkerUtils() {
|
||||
}
|
||||
|
||||
public static Drawable getDefaultPhoto(@NonNull Context context) {
|
||||
return context.getResources().getDrawable(ICON_ID);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -32,8 +31,6 @@ import de.dennisguse.opentracks.R;
|
||||
*/
|
||||
public class PreferencesUtils {
|
||||
|
||||
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
|
||||
|
||||
private PreferencesUtils() {
|
||||
}
|
||||
|
||||
@@ -43,29 +40,13 @@ public class PreferencesUtils {
|
||||
|
||||
@Deprecated
|
||||
//NOTE: is at the moment still used to determine if a track is currently recorded; better ask the service directly.
|
||||
//NOTE: This is also used to recover from a reboot, but this data should not be exposed to the whole application.
|
||||
//NOTE: This was also used to recover from a reboot, but this data should not be exposed to the whole application.
|
||||
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
|
||||
|
||||
public static long getRecordingTrackId(Context context) {
|
||||
return PreferencesUtils.getLong(context, R.string.recording_track_id_key, RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
public static final int AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT = 0;
|
||||
|
||||
public static int getAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
return PreferencesUtils.getInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void resetAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, PreferencesUtils.AUTO_RESUME_TRACK_CURRENT_RETRY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void incrementAutoResumeTrackCurrentRetryDefault(Context context) {
|
||||
PreferencesUtils.setInt(context, R.string.auto_resume_track_current_retry_key, getAutoResumeTrackCurrentRetryDefault(context) + 1);
|
||||
}
|
||||
|
||||
public static String getDefaultActivity(Context context) {
|
||||
return PreferencesUtils.getString(context, R.string.default_activity_key, context.getString(R.string.default_activity_default));
|
||||
}
|
||||
@@ -88,7 +69,7 @@ public class PreferencesUtils {
|
||||
* Compares if keyId and key belong to the same shared preference key.
|
||||
*
|
||||
* @param keyId The resource id of the key
|
||||
* @param 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) {
|
||||
@@ -211,33 +192,16 @@ public class PreferencesUtils {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if metric units.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isMetricUnits(Context context) {
|
||||
final String STATS_UNIT = context.getString(R.string.stats_units_default);
|
||||
return STATS_UNIT.equals(getString(context, R.string.stats_units_key, STATS_UNIT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the preferred rate is speed, false if the preferred rate is
|
||||
* pace.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
public static boolean isReportSpeed(Context context) {
|
||||
final String STATS_RATE_DEFAULT = context.getString(R.string.stats_rate_default);
|
||||
return STATS_RATE_DEFAULT.equals(getString(context, R.string.stats_rate_key, STATS_RATE_DEFAULT));
|
||||
}
|
||||
|
||||
public static int getAutoResumeTrackTimeout(Context context) {
|
||||
final int AUTO_RESUME_TRACK_TIMEOUT = Integer.parseInt(context.getResources().getString(R.string.auto_resume_track_timeout_default));
|
||||
return PreferencesUtils.getInt(context, R.string.auto_resume_track_timeout_key, AUTO_RESUME_TRACK_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isRecordingTrackPaused(Context context) {
|
||||
return PreferencesUtils.getBoolean(context, R.string.recording_track_paused_key, isRecordingTrackPausedDefault(context));
|
||||
}
|
||||
@@ -251,37 +215,20 @@ public class PreferencesUtils {
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddressNone(Context context) {
|
||||
return context.getString(R.string.sensor_type_value_none);
|
||||
}
|
||||
|
||||
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context) {
|
||||
return PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(getBluetoothHeartRateSensorAddress(context));
|
||||
return getBluetoothHeartRateSensorAddressNone(context).equals(getBluetoothHeartRateSensorAddress(context));
|
||||
}
|
||||
|
||||
public static boolean isBluetoothHeartRateSensorAddressDefault(Context context, String currentValue) {
|
||||
return getBluetoothHeartRateSensorAddressNone(context).equals(currentValue);
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddress(Context context) {
|
||||
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldChartShowCadence(Context context) {
|
||||
final boolean CHART_SHOW_CADENCE = context.getResources().getBoolean(R.bool.chart_show_cadence_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.chart_show_cadence_key, CHART_SHOW_CADENCE);
|
||||
}
|
||||
|
||||
public static boolean shouldChartShowElevation(Context context) {
|
||||
final boolean CHART_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.chart_show_elevation_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.chart_show_elevation_key, CHART_SHOW_ELEVATION);
|
||||
}
|
||||
|
||||
public static boolean shouldChartShowHeartRate(Context context) {
|
||||
final boolean CHART_SHOW_HEARTRATE = context.getResources().getBoolean(R.bool.chart_show_heart_rate_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.chart_show_heart_rate_key, CHART_SHOW_HEARTRATE);
|
||||
}
|
||||
|
||||
public static boolean shouldChartShowPower(Context context) {
|
||||
final boolean CHART_SHOW_POWER = context.getResources().getBoolean(R.bool.chart_show_power_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.chart_show_power_key, CHART_SHOW_POWER);
|
||||
}
|
||||
|
||||
public static boolean shouldChartShowSpeed(Context context) {
|
||||
final boolean CHART_SHOW_SPEED = context.getResources().getBoolean(R.bool.chart_show_speed_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.chart_show_speed_key, CHART_SHOW_SPEED);
|
||||
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothHeartRateSensorAddressNone(context));
|
||||
}
|
||||
|
||||
public static boolean shouldShowStatsOnLockscreen(Context context) {
|
||||
@@ -289,6 +236,11 @@ public class PreferencesUtils {
|
||||
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldKeepScreenOn(Context context) {
|
||||
final boolean DEFAULT = context.getResources().getBoolean(R.bool.stats_keep_screen_on_while_recording_default);
|
||||
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsGradeElevation(Context context) {
|
||||
final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default);
|
||||
return PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION);
|
||||
@@ -304,13 +256,7 @@ public class PreferencesUtils {
|
||||
return PreferencesUtils.getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
}
|
||||
|
||||
public static int getSplitFrequency(Context context) {
|
||||
final int SPLIT_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.split_frequency_default));
|
||||
return PreferencesUtils.getInt(context, R.string.split_frequency_key, SPLIT_FREQUENCY_DEFAULT);
|
||||
}
|
||||
|
||||
public static int getRecordingDistanceInterval(Context context) {
|
||||
final int RECORDING_DISTANCE_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
||||
return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||
}
|
||||
|
||||
@@ -341,9 +287,6 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static int getRecordingGPSAccuracy(Context context) {
|
||||
final int RECORDING_GPS_ACCURACY = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
return PreferencesUtils.getInt(context, R.string.recording_gps_accuracy_key, RECORDING_GPS_ACCURACY);
|
||||
|
||||
@@ -1,283 +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.app.Activity;
|
||||
import android.location.Location;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
|
||||
/**
|
||||
* Utilities for updating the statistics UI labels and values.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class StatsUtils {
|
||||
|
||||
private static final String GRADE_PERCENTAGE = "%";
|
||||
private static final String GRADE_FORMAT = "%1$d";
|
||||
|
||||
private StatsUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location values.
|
||||
*
|
||||
* @param activity the activity for finding views
|
||||
* @param location the location
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
public static void setLocationValues(Activity activity, Location location, boolean isRecording) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
|
||||
|
||||
// Set speed/pace
|
||||
View speedContainer = activity.findViewById(R.id.stats_speed);
|
||||
speedContainer.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
TextView speedLabel = activity.findViewById(R.id.stats_speed_label);
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
double speed = location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
|
||||
TextView speedValue = activity.findViewById(R.id.stats_speed_value);
|
||||
speedValue.setText(parts.first);
|
||||
|
||||
TextView speedUnit = activity.findViewById(R.id.stats_speed_unit);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(activity);
|
||||
View elevationContainer = activity.findViewById(R.id.stats_elevation);
|
||||
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showGradeElevation) {
|
||||
double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, altitude, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_current_value);
|
||||
elevationValue.setText(parts.first);
|
||||
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_current_unit);
|
||||
elevationUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.isStatsShowCoordinate(activity);
|
||||
|
||||
View coordinateSeparator = activity.findViewById(R.id.stats_coordinate_separator);
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
|
||||
View coordinateContainer = activity.findViewById(R.id.stats_coordinate_container);
|
||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
double latitude = location != null ? location.getLatitude() : Double.NaN;
|
||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? activity.getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||
TextView latitudeValue = activity.findViewById(R.id.stats_latitude_value);
|
||||
latitudeValue.setText(latitudeText);
|
||||
|
||||
double longitude = location != null ? location.getLongitude() : Double.NaN;
|
||||
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? activity.getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
|
||||
TextView longitudeValue = activity.findViewById(R.id.stats_longitude_value);
|
||||
longitudeValue.setText(longitudeText);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSensorData(Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
// heart rate
|
||||
int isVisible = View.VISIBLE;
|
||||
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(activity)) {
|
||||
isVisible = View.INVISIBLE;
|
||||
}
|
||||
activity.findViewById(R.id.stats_sensor_container).setVisibility(isVisible);
|
||||
|
||||
if (isRecording) {
|
||||
TextView heartRateValue = activity.findViewById(R.id.stats_sensor_heart_rate_value);
|
||||
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||
String heartRate = activity.getString(R.string.value_unknown);
|
||||
String sensor = activity.getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.isRecent(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)) {
|
||||
sensor = sensorDataSet.getSensorName();
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
setBluetoothHeartRateSensorName(activity, sensor, isRecording);
|
||||
heartRateSensor.setText(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBluetoothHeartRateSensorName(Activity activity, String sensorName, boolean isRecording) {
|
||||
if (isRecording) {
|
||||
TextView heartRateSensor = activity.findViewById(R.id.stats_sensor_heart_rate_sensor_value);
|
||||
heartRateSensor.setText(sensorName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total time value.
|
||||
*
|
||||
* @param activity the activity
|
||||
* @param totalTime the total time
|
||||
*/
|
||||
public static void setTotalTimeValue(Activity activity, long totalTime) {
|
||||
TextView totalTimeValue = activity.findViewById(R.id.stats_total_time_value);
|
||||
totalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the trip statistics values.
|
||||
*
|
||||
* @param activity the activity for finding views
|
||||
* @param tripStatistics the trip statistics
|
||||
* @param trackIconValue the track icon value or null to hide the track icon spinner
|
||||
*/
|
||||
public static void setTripStatisticsValues(Activity activity, TripStatistics tripStatistics, String trackIconValue) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(activity);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(activity);
|
||||
boolean isRecording = PreferencesUtils.isRecording(activity);
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = tripStatistics == null ? Double.NaN : tripStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(activity, totalDistance, metricUnits);
|
||||
|
||||
TextView distanceValue = activity.findViewById(R.id.stats_distance_value);
|
||||
distanceValue.setText(parts.first);
|
||||
|
||||
TextView distanceUnit = activity.findViewById(R.id.stats_distance_unit);
|
||||
distanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
activity.findViewById(R.id.stats_activity_type_label).setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||
|
||||
Spinner spinner = activity.findViewById(R.id.stats_activity_type_icon);
|
||||
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
|
||||
if (trackIconValue != null) {
|
||||
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
|
||||
}
|
||||
spinner.setEnabled(isRecording);
|
||||
}
|
||||
|
||||
// Set time
|
||||
if (tripStatistics != null) {
|
||||
setTotalTimeValue(activity, tripStatistics.getTotalTime());
|
||||
|
||||
TextView movingTimeValue = activity.findViewById(R.id.stats_moving_time_value);
|
||||
movingTimeValue.setText(StringUtils.formatElapsedTime(tripStatistics.getMovingTime()));
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
|
||||
TextView speedLabel = activity.findViewById(R.id.stats_average_speed_label);
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
TextView speedValue = activity.findViewById(R.id.stats_average_speed_value);
|
||||
speedValue.setText(parts.first);
|
||||
TextView speedUnit = activity.findViewById(R.id.stats_average_speed_unit);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
|
||||
|
||||
TextView speedLabel = activity.findViewById(R.id.stats_max_speed_label);
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
TextView speedValue = activity.findViewById(R.id.stats_max_speed_value);
|
||||
speedValue.setText(parts.first);
|
||||
TextView speedUnit = activity.findViewById(R.id.stats_max_speed_unit);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
|
||||
|
||||
TextView speedLabel = activity.findViewById(R.id.stats_average_speed_label);
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
TextView speedValue = activity.findViewById(R.id.stats_average_speed_value);
|
||||
speedValue.setText(parts.first);
|
||||
TextView speedUnit = activity.findViewById(R.id.stats_average_speed_unit);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
|
||||
TextView speedLabel = activity.findViewById(R.id.stats_moving_speed_label);
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(activity, speed, metricUnits, reportSpeed);
|
||||
TextView speedValue = activity.findViewById(R.id.stats_moving_speed_value);
|
||||
speedValue.setText(parts.first);
|
||||
TextView speedUnit = activity.findViewById(R.id.stats_moving_speed_unit);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
|
||||
// Set elevation
|
||||
{
|
||||
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(activity);
|
||||
View gradeElevationSeparator = activity.findViewById(R.id.stats_elevation_separator);
|
||||
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
View gradeElevationContainer = activity.findViewById(R.id.stats_elevation_container);
|
||||
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
{
|
||||
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMinElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_min_value);
|
||||
elevationValue.setText(parts.first);
|
||||
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_min_unit);
|
||||
elevationUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
{
|
||||
double elevation = tripStatistics == null ? Double.NaN : tripStatistics.getMaxElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(activity, elevation, metricUnits);
|
||||
|
||||
TextView elevationValue = activity.findViewById(R.id.stats_elevation_max_value);
|
||||
elevationValue.setText(parts.first);
|
||||
TextView elevationUnit = activity.findViewById(R.id.stats_elevation_max_unit);
|
||||
elevationUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class StringUtils {
|
||||
/**
|
||||
* Format a decimal number while removing trailing zeros of the decimal part (if present).
|
||||
*/
|
||||
static String formatDecimal(double value, int decimalPlaces) {
|
||||
public static String formatDecimal(double value, int decimalPlaces) {
|
||||
if (decimalPlaces < 1) {
|
||||
return Long.toString(Math.round(value));
|
||||
}
|
||||
@@ -158,7 +158,7 @@ public class StringUtils {
|
||||
* @param metricUnits true to use metric unit
|
||||
* @return the formatted distance (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
static Pair<String, String> getDistanceParts(Context context, double distance, boolean metricUnits) {
|
||||
public static Pair<String, String> getDistanceParts(Context context, double distance, boolean metricUnits) {
|
||||
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
|
||||
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 62 KiB |
@@ -17,80 +17,87 @@ limitations under the License.
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<include layout="@layout/track_controller" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/track_list"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0" />
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/track_list_empty_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
<include layout="@layout/track_controller" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/track_list_arrow_long"
|
||||
android:layout_width="wrap_content"
|
||||
<ListView
|
||||
android:id="@+id/track_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="16dp">
|
||||
android:layout_weight="1.0" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<RelativeLayout
|
||||
android:id="@+id/track_list_empty_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<ImageView
|
||||
<LinearLayout
|
||||
android:id="@+id/track_list_arrow_long"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="10"
|
||||
android:contentDescription="@string/image_arrow"
|
||||
android:src="@drawable/ic_arrow_long" />
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="17" />
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toRightOf="@id/track_list_arrow_long"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="10"
|
||||
android:contentDescription="@string/image_arrow"
|
||||
android:src="@drawable/ic_arrow_long" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="4" />
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="17" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="24">
|
||||
|
||||
<TextView
|
||||
style="@style/TextLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight=".7"
|
||||
android:text="@string/track_list_empty_message"
|
||||
android:textColor="@color/list_empty_text" />
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toRightOf="@id/track_list_arrow_long"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight=".3" />
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="4" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="24">
|
||||
|
||||
<TextView
|
||||
style="@style/TextLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight=".7"
|
||||
android:text="@string/track_list_empty_message"
|
||||
android:textColor="@color/list_empty_text" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight=".3" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -17,85 +17,50 @@ limitations under the License.
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/marker_detail_waypoint"
|
||||
<ImageView
|
||||
android:id="@+id/marker_detail_waypoint_photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:contentDescription="@string/image_photo_marker"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- A gradient on top of the text -->
|
||||
<ImageView
|
||||
android:id="@+id/marker_detail_waypoint_photo"
|
||||
android:id="@+id/marker_detail_waypoint_text_gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:alpha=".6"
|
||||
android:contentDescription="@string/image_photo_marker"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- A gradient on top of the text -->
|
||||
<ImageView
|
||||
android:id="@+id/marker_detail_waypoint_text_gradient"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha=".6"
|
||||
android:contentDescription="@string/image_photo_marker"
|
||||
android:src="@drawable/text_gradient" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/marker_detail_waypoint_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_waypoint_name"
|
||||
style="@style/TextMedium"
|
||||
android:paddingTop="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_waypoint_category"
|
||||
style="@style/TextSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_waypoint_description"
|
||||
style="@style/TextSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_waypoint_location"
|
||||
style="@style/TextSmall" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/marker_detail_statistics"
|
||||
style="@style/StatsScrollView">
|
||||
android:src="@drawable/text_gradient" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/marker_detail_waypoint_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_statistics_name"
|
||||
android:id="@+id/marker_detail_waypoint_name"
|
||||
style="@style/TextMedium"
|
||||
android:paddingTop="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_statistics_location"
|
||||
android:id="@+id/marker_detail_waypoint_category"
|
||||
style="@style/TextSmall" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextSmall"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="@string/marker_detail_statistics_title" />
|
||||
android:id="@+id/marker_detail_waypoint_description"
|
||||
style="@style/TextSmall" />
|
||||
|
||||
<include layout="@layout/stats_content" />
|
||||
<TextView
|
||||
android:id="@+id/marker_detail_waypoint_location"
|
||||
style="@style/TextSmall" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
@@ -41,7 +41,7 @@ limitations under the License.
|
||||
<EditText
|
||||
android:id="@+id/marker_edit_statistics_name"
|
||||
style="@style/EditNameMarker"
|
||||
android:drawableLeft="@drawable/ic_marker_yellow_pushpin"
|
||||
android:drawableLeft="@drawable/ic_marker_blue_pushpin"
|
||||
android:hint="@string/generic_name"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no" />
|
||||
|
||||
@@ -21,6 +21,386 @@ limitations under the License.
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/stats_content" />
|
||||
<!-- Distance and activity type -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- Distance -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_distance" />
|
||||
|
||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Activity type -->
|
||||
<include
|
||||
android:id="@+id/stats_activity_type"
|
||||
layout="@layout/stats_activity_type" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Total time and moving time -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Total time -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_total_time" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_total_time_value"
|
||||
style="@style/StatsLargeValue" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Moving time -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_moving_time" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Speed/pace and average -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Speed/pace -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_speed"
|
||||
style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Average -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Speed max and moving -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Speed max -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Speed moving -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/stats_elevation_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_elevation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
<!-- This is a placeholder -->
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Elevation -->
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_elevation" />
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<!-- Current elevation -->
|
||||
<TableRow
|
||||
android:id="@+id/stats_elevation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
</TableRow>
|
||||
|
||||
<!-- Minimal elevation -->
|
||||
<TableRow
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_min_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_min_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/stats_min" />
|
||||
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<!-- Maximal elevation -->
|
||||
<TableRow
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_max_value"
|
||||
style="@style/StatsSmallValue" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_max_unit"
|
||||
style="@style/StatsUnit" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/stats_max" />
|
||||
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_coordinate_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Latitude and longitude -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_coordinate_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_latitude" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_latitude_value"
|
||||
style="@style/StatsSmallValue" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_longitude" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_longitude_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/stats_sensor_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Sensor data: heart rate-->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_sensor_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/sensor_state_heart_rate" />
|
||||
|
||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_heart_rate_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsUnit"
|
||||
android:text="@string/sensor_unit_beats_per_minute" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/settings_sensor_bluetooth_sensor" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_heart_rate_sensor_value"
|
||||
style="@style/StatsTinyValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -1,396 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Distance and activity type -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- Distance -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_distance" />
|
||||
|
||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Activity type -->
|
||||
<include
|
||||
android:id="@+id/stats_activity_type"
|
||||
layout="@layout/stats_activity_type" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Total time and moving time -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Total time -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_total_time" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_total_time_value"
|
||||
style="@style/StatsLargeValue" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Moving time -->
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_moving_time" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Speed/pace and average -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Speed/pace -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_speed"
|
||||
style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Average -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Speed max and moving -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Speed max -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Speed moving -->
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_label"
|
||||
style="@style/StatsSmallLabel" />
|
||||
|
||||
<LinearLayout style="@style/StatsSmallValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/stats_elevation_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_elevation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
<!-- This is a placeholder -->
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Elevation -->
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_elevation" />
|
||||
|
||||
<TableLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<!-- Current elevation -->
|
||||
<TableRow
|
||||
android:id="@+id/stats_elevation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_current_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
</TableRow>
|
||||
|
||||
<!-- Minimal elevation -->
|
||||
<TableRow
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_min_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_min_unit"
|
||||
style="@style/StatsUnit" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/stats_min" />
|
||||
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<!-- Maximal elevation -->
|
||||
<TableRow
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_max_value"
|
||||
style="@style/StatsSmallValue" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_elevation_max_unit"
|
||||
style="@style/StatsUnit" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/StatsSmallValueContainer"
|
||||
android:layout_gravity="start|bottom">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallValue"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/stats_max" />
|
||||
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_coordinate_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Latitude and longitude -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_coordinate_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_latitude" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_latitude_value"
|
||||
style="@style/StatsSmallValue" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/StatsSmallItemContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_longitude" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_longitude_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/stats_sensor_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
|
||||
<!-- Sensor data: heart rate-->
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_sensor_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_heart_rate_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/sensor_state_heart_rate" />
|
||||
|
||||
<LinearLayout style="@style/StatsLargeValueContainer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_heart_rate_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
<TextView
|
||||
style="@style/StatsUnit"
|
||||
android:text="@string/sensor_unit_beats_per_minute" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/StatsSmallGroupContainer">
|
||||
|
||||
<TextView
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/settings_sensor_bluetooth_sensor" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_heart_rate_sensor_value"
|
||||
style="@style/StatsTinyValue"
|
||||
android:value="@string/value_unknown" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -31,10 +31,6 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_marker_show_24dp"
|
||||
android:title="@string/menu_markers"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/track_list_delete_all"
|
||||
android:icon="@drawable/ic_delete_forever_24dp"
|
||||
android:title="@string/menu_delete_all" />
|
||||
<item
|
||||
android:id="@+id/track_list_settings"
|
||||
android:icon="@drawable/ic_settings_24dp"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
<dimen name="widget_margin">0dp</dimen>
|
||||
</resources>
|
||||
@@ -1,21 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Do not translate anything in this file directly. -->
|
||||
<string name="chart_show_cadence_key" translatable="false">chartShowCadence</string>
|
||||
<bool name="chart_show_cadence_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_elevation_key" translatable="false">chartShowElevation</string>
|
||||
<bool name="chart_show_elevation_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_heart_rate_key" translatable="false">chartShowHeartRate</string>
|
||||
<bool name="chart_show_heart_rate_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_power_key" translatable="false">chartShowPower</string>
|
||||
<bool name="chart_show_power_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_speed_key" translatable="false">chartShowSpeed</string>
|
||||
<bool name="chart_show_speed_default" translatable="false">true</bool>
|
||||
|
||||
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
|
||||
<bool name="stats_show_coordinate_default" translatable="false">false</bool>
|
||||
|
||||
@@ -25,6 +10,9 @@
|
||||
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
||||
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
<string name="stats_keep_screen_on_while_recording_key" translatable="false">trackdetail_keep_screen_on_while_recording</string>
|
||||
<bool name="stats_keep_screen_on_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||
@@ -102,40 +90,6 @@
|
||||
<item>@string/activity_type_wind_surfing</item>
|
||||
</string-array>
|
||||
|
||||
<string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>
|
||||
<string name="auto_resume_track_timeout_default" translatable="false">10</string>
|
||||
<string name="auto_resume_track_timeout_always" translatable="false">-1</string>
|
||||
<string name="auto_resume_track_timeout_never" translatable="false">0</string>
|
||||
<string-array name="auto_resume_track_timeout_values">
|
||||
<item>@string/auto_resume_track_timeout_never</item>
|
||||
<item>5</item>
|
||||
<item>@string/auto_resume_track_timeout_default</item>
|
||||
<item>15</item>
|
||||
<item>@string/auto_resume_track_timeout_always</item>
|
||||
</string-array>
|
||||
|
||||
<string name="split_frequency_key" translatable="false">splitFrequency</string>
|
||||
<string name="split_frequency_default" translatable="false">@string/frequency_off</string>
|
||||
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
||||
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
|
||||
<string name="frequency_off" translatable="false">0</string>
|
||||
<string-array name="frequency_values">
|
||||
<item>@string/frequency_off</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>15</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>-1</item>
|
||||
<item>-5</item>
|
||||
<item>-10</item>
|
||||
<item>-25</item>
|
||||
<item>-50</item>
|
||||
<item>-100</item>
|
||||
</string-array>
|
||||
|
||||
<string name="recording_distance_interval_key" translatable="false">recordingDistanceInterval</string>
|
||||
<string name="recording_distance_interval_default" translatable="false">10</string>
|
||||
<string-array name="recording_distance_interval_values">
|
||||
|
||||
@@ -1,18 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- This file contains old and now unused settings. -->
|
||||
<!-- It is recommended not to reuse the keys. -->
|
||||
|
||||
<string name="chart_x_axis_key" translatable="false">chartXAxis</string>
|
||||
<string name="chart_x_axis_default" translatable="false">@string/chart_x_axis_distance</string>
|
||||
<string-array name="chart_x_axis_values">
|
||||
<string name="chart_x_axis_key" translatable="false" tools:keep="@string/chart_x_axis_key">chartXAxis</string>
|
||||
<string name="chart_x_axis_default" translatable="false" tools:keep="@string/chart_x_axis_default">@string/chart_x_axis_distance</string>
|
||||
<string-array name="chart_x_axis_values" tools:keep="@array/chart_x_axis_values">
|
||||
<item>@string/chart_x_axis_distance</item>
|
||||
<item>@string/chart_x_axis_time</item>
|
||||
</string-array>
|
||||
<string name="chart_x_axis_time" translatable="false">TIME</string>
|
||||
<string name="chart_x_axis_distance" translatable="false">DISTANCE</string>
|
||||
<string-array name="chart_x_axis_options">
|
||||
<string name="chart_x_axis_time" translatable="false" tools:keep="@string/chart_x_axis_time">TIME</string>
|
||||
<string name="chart_x_axis_distance" translatable="false" tools:keep="@string/chart_x_axis_distance">DISTANCE</string>
|
||||
<string-array name="chart_x_axis_options" tools:keep="@array/chart_x_axis_options">
|
||||
<item>@string/settings_chart_by_distance</item>
|
||||
<item>@string/settings_chart_by_time</item>
|
||||
</string-array>
|
||||
|
||||
<string name="split_frequency_key" translatable="false" tools:keep="@string/split_frequency_key">splitFrequency</string>
|
||||
<string name="split_frequency_default" translatable="false" tools:keep="@string/split_frequency_default">@string/frequency_off</string>
|
||||
<string name="voice_frequency_key" translatable="false">voiceFrequency</string>
|
||||
<string name="voice_frequency_default" translatable="false">@string/frequency_off</string>
|
||||
<string name="frequency_off" translatable="false">0</string>
|
||||
<string-array name="frequency_values">
|
||||
<item>@string/frequency_off</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>15</item>
|
||||
<item>30</item>
|
||||
<item>60</item>
|
||||
<item>-1</item>
|
||||
<item>-5</item>
|
||||
<item>-10</item>
|
||||
<item>-25</item>
|
||||
<item>-50</item>
|
||||
<item>-100</item>
|
||||
</string-array>
|
||||
|
||||
<string name="chart_show_cadence_key" translatable="false">chartShowCadence</string>
|
||||
<bool name="chart_show_cadence_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_elevation_key" translatable="false">chartShowElevation</string>
|
||||
<bool name="chart_show_elevation_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_heart_rate_key" translatable="false">chartShowHeartRate</string>
|
||||
<bool name="chart_show_heart_rate_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_power_key" translatable="false">chartShowPower</string>
|
||||
<bool name="chart_show_power_default" translatable="false">true</bool>
|
||||
|
||||
<string name="chart_show_speed_key" translatable="false">chartShowSpeed</string>
|
||||
<bool name="chart_show_speed_default" translatable="false">true</bool>
|
||||
|
||||
<!-- <string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>-->
|
||||
<!-- <string name="auto_resume_track_timeout_default" translatable="false">10</string>-->
|
||||
<!-- <string name="auto_resume_track_timeout_always" translatable="false">-1</string>-->
|
||||
<!-- <string name="auto_resume_track_timeout_never" translatable="false">0</string>-->
|
||||
<!-- <string-array name="auto_resume_track_timeout_values">-->
|
||||
<!-- <item>@string/auto_resume_track_timeout_never</item>-->
|
||||
<!-- <item>5</item>-->
|
||||
<!-- <item>@string/auto_resume_track_timeout_default</item>-->
|
||||
<!-- <item>15</item>-->
|
||||
<!-- <item>@string/auto_resume_track_timeout_always</item>-->
|
||||
<!-- </string-array>-->
|
||||
</resources>
|
||||
@@ -1194,6 +1194,13 @@ limitations under the License.
|
||||
While recording, show statistics without unlocking the device.
|
||||
</string>
|
||||
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title" translation_description="In the 'Settings' page, the option to change the 'show on lockscreen' behavior while recording.">
|
||||
Keep screen on
|
||||
</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary" translation_description="In the 'Settings' page, the option to change the 'show on lockscreen' behavior while recording.">
|
||||
While recording, keep the screen on.
|
||||
</string>
|
||||
|
||||
<string name="settings_recording" translation_description="In the 'Settings' page, the option to change the recording settings.
|
||||
|
||||
Character limit: ~26 (1 line)">
|
||||
|
||||
@@ -53,39 +53,23 @@ limitations under the License.
|
||||
android:title="@string/settings_stats_coordinate" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/track_detail_chart_tab">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/chart_show_elevation_key"
|
||||
android:title="@string/stats_elevation" />
|
||||
<!-- title set in code -->
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/chart_show_speed_key" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/chart_show_heart_rate_key"
|
||||
android:title="@string/sensor_state_heart_rate" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_recording">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="@bool/stats_show_on_lockscreen_while_recording_default"
|
||||
android:key="@string/stats_show_on_lockscreen_while_recording_key"
|
||||
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
|
||||
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="@bool/stats_keep_screen_on_while_recording_default"
|
||||
android:key="@string/stats_keep_screen_on_while_recording_key"
|
||||
android:summary="@string/settings_recording_keep_screen_on_while_recording_summary"
|
||||
android:title="@string/settings_recording_keep_screen_on_while_recording_title" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/voice_frequency_default"
|
||||
android:entryValues="@array/frequency_values"
|
||||
android:key="@string/voice_frequency_key"
|
||||
android:title="@string/menu_voice_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/split_frequency_default"
|
||||
android:entryValues="@array/frequency_values"
|
||||
android:key="@string/split_frequency_key"
|
||||
android:title="@string/menu_split_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/track_name_default"
|
||||
android:entries="@array/track_name_options"
|
||||
@@ -117,13 +101,6 @@ limitations under the License.
|
||||
android:key="@string/recording_gps_accuracy_key"
|
||||
android:title="@string/settings_recording_min_required_accuracy_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/auto_resume_track_timeout_default"
|
||||
android:entries="@array/auto_resume_track_timeout_values"
|
||||
android:entryValues="@array/auto_resume_track_timeout_values"
|
||||
android:key="@string/auto_resume_track_timeout_key"
|
||||
android:title="@string/settings_recording_auto_resume_track_timeout_title"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<de.dennisguse.opentracks.settings.ActivityTypePreference
|
||||
android:defaultValue="@string/default_activity_default"
|
||||
android:key="@string/default_activity_key"
|
||||
|
||||