forked from upstream-mirrors/OpenTracks
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bafc7d4a1 | |||
| be58b48d20 | |||
| 303abac980 | |||
| d1a459efc2 | |||
| 96069e6f09 | |||
| 301a721329 | |||
| e8c6801a32 | |||
| 355f3ef614 | |||
| eaa6083586 | |||
| a0c5d02c51 | |||
| 76f9f5fc82 | |||
| 8e2598e93b |
@@ -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
|
||||||
-21
@@ -151,27 +151,6 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
fullRecordingSession();
|
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
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="3162"
|
android:versionCode="3175"
|
||||||
android:versionName="v3.2.8"> <!-- For FDroid. -->
|
android:versionName="v3.2.9"> <!-- For FDroid. -->
|
||||||
<!-- Permission to initialize services -->
|
<!-- Permission to initialize services -->
|
||||||
<application
|
<application
|
||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
@@ -145,34 +145,17 @@ limitations under the License.
|
|||||||
android:name="android.hardware.location.gps"
|
android:name="android.hardware.location.gps"
|
||||||
android:required="true" />
|
android:required="true" />
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.bluetooth"
|
android:name="android.hardware.android.hardware.bluetooth_le"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
<!-- Permissions to access sensors -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="false" />
|
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
|
<uses-feature
|
||||||
android:name="android.hardware.sensor.compass"
|
android:name="android.hardware.sensor.compass"
|
||||||
android:required="false" />
|
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
|
<uses-feature
|
||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
android:required="false" />
|
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.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
@@ -180,9 +163,6 @@ limitations under the License.
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<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.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
|||||||
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
waypointSection = findViewById(R.id.marker_edit_waypoint_section);
|
||||||
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
||||||
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
waypointMarkerType = findViewById(R.id.marker_edit_waypoint_marker_type);
|
||||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
|
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||||
this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
|
||||||
waypointMarkerType.setAdapter(adapter);
|
waypointMarkerType.setAdapter(adapter);
|
||||||
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
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.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.content.WaypointsColumns;
|
import de.dennisguse.opentracks.content.WaypointsColumns;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +152,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
|
|
||||||
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
|
||||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||||
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
|
||||||
@@ -161,12 +160,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
|
||||||
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
|
||||||
|
|
||||||
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)] == WaypointType.STATISTICS;
|
int iconId = MarkerUtils.ICON_ID;
|
||||||
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin;
|
|
||||||
String name = cursor.getString(nameIndex);
|
String name = cursor.getString(nameIndex);
|
||||||
long time = cursor.getLong(timeIndex);
|
long time = cursor.getLong(timeIndex);
|
||||||
String category = statistics ? null : cursor.getString(categoryIndex);
|
String category = cursor.getString(categoryIndex);
|
||||||
String description = statistics ? null : cursor.getString(descriptionIndex);
|
String description = cursor.getString(descriptionIndex);
|
||||||
String photoUrl = cursor.getString(photoUrlIndex);
|
String photoUrl = cursor.getString(photoUrlIndex);
|
||||||
double latitude = cursor.getDouble(latitudeIndex);
|
double latitude = cursor.getDouble(latitudeIndex);
|
||||||
double longitude = cursor.getDouble(longitudeIndex);
|
double longitude = cursor.getDouble(longitudeIndex);
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
|
|||||||
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
import de.dennisguse.opentracks.content.Track;
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
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.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
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_RECORDING_FIELD, false);
|
||||||
resultMap.put(IS_PAUSED_FIELD, true);
|
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(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_marker);
|
||||||
resultMap.put(NAME_FIELD, waypoint.getName());
|
resultMap.put(NAME_FIELD, waypoint.getName());
|
||||||
// Display the marker's track name in the total time field
|
// 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(TOTAL_DISTANCE_FIELD, null);
|
||||||
resultMap.put(MARKER_COUNT_FIELD, 0);
|
resultMap.put(MARKER_COUNT_FIELD, 0);
|
||||||
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
|
||||||
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
|
resultMap.put(CATEGORY_FIELD, waypoint.getCategory());
|
||||||
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription());
|
resultMap.put(DESCRIPTION_FIELD, waypoint.getDescription());
|
||||||
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
|
||||||
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
|
||||||
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
|
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.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
@@ -127,16 +126,24 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_id_key, key)) {
|
||||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackDetailActivity.this);
|
||||||
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.recording_track_paused_key, key)) {
|
||||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackDetailActivity.this);
|
||||||
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtils.isKey(TrackDetailActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||||
|
setScreenOnPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -227,11 +234,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
@Override
|
@Override
|
||||||
public void onAttachedToWindow() {
|
public void onAttachedToWindow() {
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
|
setScreenOnPolicy();
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLockscreenPolicy() {
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
setShowWhenLocked(showOnLockScreen);
|
setShowWhenLocked(showOnLockScreen);
|
||||||
@@ -242,6 +251,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
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -413,22 +433,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
|||||||
return trackDataHub;
|
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) {
|
private void handleIntent(Intent intent) {
|
||||||
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||||
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import de.dennisguse.opentracks.R;
|
|||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
@@ -263,12 +264,9 @@ public class ChartView extends View {
|
|||||||
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
pointer = context.getResources().getDrawable(R.drawable.ic_logo_color_24dp);
|
||||||
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
pointer.setBounds(0, 0, pointer.getIntrinsicWidth(), pointer.getIntrinsicHeight());
|
||||||
|
|
||||||
Drawable statisticsMarker = getResources().getDrawable(R.drawable.ic_marker_yellow_pushpin);
|
Drawable waypointMarker = MarkerUtils.getDefaultPhoto(context);
|
||||||
markerWidth = statisticsMarker.getIntrinsicWidth();
|
markerWidth = waypointMarker.getIntrinsicWidth();
|
||||||
markerHeight = statisticsMarker.getIntrinsicHeight();
|
markerHeight = waypointMarker.getIntrinsicHeight();
|
||||||
statisticsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
|
||||||
|
|
||||||
Drawable waypointMarker = getResources().getDrawable(R.drawable.ic_marker_blue_pushpin);
|
|
||||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
scroller = new Scroller(context);
|
scroller = new Scroller(context);
|
||||||
|
|||||||
@@ -466,12 +466,8 @@ public class TrackDataHub implements DataSourceListener {
|
|||||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LocationUtils.isValidLocation(location)) {
|
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
|
||||||
// TODO: also include the last valid point before a split
|
includeNextPoint = true;
|
||||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
|
||||||
trackDataListener.onSegmentSplit(location);
|
|
||||||
includeNextPoint = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Also include the last point if the selected track is not recording.
|
// Also include the last point if the selected track is not recording.
|
||||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||||
|
|||||||
@@ -51,14 +51,9 @@ public interface TrackDataListener {
|
|||||||
*/
|
*/
|
||||||
void onSampledOutTrackPoint(Location location);
|
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.
|
* 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();
|
void onNewTrackPointsDone();
|
||||||
|
|
||||||
|
|||||||
@@ -198,13 +198,6 @@ public abstract class ChartFragment extends Fragment implements TrackDataListene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSegmentSplit(Location location) {
|
|
||||||
if (isResumed()) {
|
|
||||||
fillDataPoint(location, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewTrackPointsDone() {
|
public void onNewTrackPointsDone() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
|
|||||||
@@ -41,12 +41,10 @@ import androidx.fragment.app.FragmentActivity;
|
|||||||
import de.dennisguse.opentracks.MarkerEditActivity;
|
import de.dennisguse.opentracks.MarkerEditActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.Track;
|
|
||||||
import de.dennisguse.opentracks.content.Waypoint;
|
import de.dennisguse.opentracks.content.Waypoint;
|
||||||
import de.dennisguse.opentracks.content.Waypoint.WaypointType;
|
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.StatsUtils;
|
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
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 static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND;
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private Handler handler;
|
private Handler handler;
|
||||||
private ImageView photo;
|
private ImageView photoView;
|
||||||
private ImageView textGradient;
|
private ImageView textGradient;
|
||||||
private LinearLayout waypointInfo;
|
private LinearLayout waypointInfo;
|
||||||
private Waypoint waypoint;
|
private Waypoint waypoint;
|
||||||
@@ -119,11 +117,11 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.marker_detail_fragment, container, false);
|
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);
|
textGradient = view.findViewById(R.id.marker_detail_waypoint_text_gradient);
|
||||||
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
waypointInfo = view.findViewById(R.id.marker_detail_waypoint_info);
|
||||||
|
|
||||||
photo.setOnClickListener(new View.OnClickListener() {
|
photoView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
handler.removeCallbacks(hideText);
|
handler.removeCallbacks(hideText);
|
||||||
@@ -156,11 +154,8 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void setMenuVisibility(boolean menuVisible) {
|
public void setMenuVisibility(boolean menuVisible) {
|
||||||
super.setMenuVisibility(menuVisible);
|
super.setMenuVisibility(menuVisible);
|
||||||
/*
|
// View pager caches the neighboring fragments in the resumed state.
|
||||||
* View pager caches the neighboring fragments in the resumed state. If
|
// If becoming visible from the resumed state, update the UI to display the text above the image.
|
||||||
* becoming visible from the resumed state, update the UI to display the
|
|
||||||
* text above the image.
|
|
||||||
*/
|
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
if (menuVisible) {
|
if (menuVisible) {
|
||||||
updateUi();
|
updateUi();
|
||||||
@@ -214,47 +209,25 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the UI.
|
|
||||||
*/
|
|
||||||
private void updateUi() {
|
private void updateUi() {
|
||||||
View waypointView = getView().findViewById(R.id.marker_detail_waypoint);
|
boolean hasPhoto = waypoint.hasPhoto();
|
||||||
View statisticsView = getView().findViewById(R.id.marker_detail_statistics);
|
if (hasPhoto) {
|
||||||
|
handler.removeCallbacks(hideText);
|
||||||
boolean isWaypoint = waypoint.getType() == WaypointType.WAYPOINT;
|
photoView.setImageURI(waypoint.getPhotoURI());
|
||||||
waypointView.setVisibility(isWaypoint ? View.VISIBLE : View.GONE);
|
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
setName(R.id.marker_detail_statistics_name, false);
|
photoView.setImageResource(MarkerUtils.ICON_ID);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private void setName(int resId, boolean addShadow) {
|
||||||
|
|||||||
@@ -20,16 +20,18 @@ import android.location.Location;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
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.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
import de.dennisguse.opentracks.services.ITrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.LocationUtils;
|
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.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
@@ -59,6 +63,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
|
|
||||||
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
public static final String STATS_FRAGMENT_TAG = StatsFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final long UI_UPDATE_INTERVAL = UnitConversions.ONE_SECOND;
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
private Handler handlerUpdateUI;
|
private Handler handlerUpdateUI;
|
||||||
|
|
||||||
@@ -67,18 +73,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
private Location lastLocation = null;
|
private Location lastLocation = null;
|
||||||
private TripStatistics lastTripStatistics = 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 String category = "";
|
||||||
private int recordingGpsAccuracy;
|
private int recordingGpsAccuracy;
|
||||||
|
|
||||||
@@ -87,6 +81,111 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
return inflater.inflate(R.layout.stats, container, false);
|
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
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
@@ -125,10 +224,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||||
trackRecordingServiceConnection.startConnection(getContext());
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
|
|
||||||
updateUi(getActivity());
|
handlerUpdateUI.post(updateUIeachSecond);
|
||||||
if (isSelectedTrackRecording()) {
|
|
||||||
handlerUpdateUI.post(updateUIeachSecond);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,6 +241,60 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
trackRecordingServiceConnection = null;
|
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
|
@Override
|
||||||
public void onTrackUpdated(final Track track) {
|
public void onTrackUpdated(final Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -154,7 +304,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
||||||
category = track != null ? track.getCategory() : "";
|
category = track != null ? track.getCategory() : "";
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -176,11 +326,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
lastLocation = location;
|
lastLocation = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSegmentSplit(Location location) {
|
|
||||||
// We don't care.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewTrackPointsDone() {
|
public void onNewTrackPointsDone() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -200,7 +345,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
lastLocation = null;
|
lastLocation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StatsUtils.setLocationValues(getActivity(), lastLocation, isSelectedTrackRecording());
|
setLocationValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -229,7 +374,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -244,7 +389,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
updateUi(getActivity());
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -300,6 +445,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
|
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}.
|
* Tries to fetch most recent {@link SensorDataSet} {@link de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager}.
|
||||||
*/
|
*/
|
||||||
@@ -314,16 +465,178 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
|||||||
sensorDataSet = trackRecordingService.getSensorData();
|
sensorDataSet = trackRecordingService.getSensorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatsUtils.setSensorData(getActivity(), sensorDataSet, isSelectedTrackRecording());
|
setHeartRateSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setHeartRateSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||||
* Updates the UI.
|
// heart rate
|
||||||
*/
|
int isVisible = View.VISIBLE;
|
||||||
private void updateUi(FragmentActivity activity) {
|
if (!isRecording || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext())) {
|
||||||
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
|
isVisible = View.INVISIBLE;
|
||||||
StatsUtils.setTripStatisticsValues(activity, lastTripStatistics, trackIconValue);
|
}
|
||||||
StatsUtils.setLocationValues(activity, lastLocation, isSelectedTrackRecording());
|
sensorContainerView.setVisibility(isVisible);
|
||||||
updateSensorDataUI();
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|||||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
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.TripStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
@@ -95,7 +94,6 @@ public class TrackRecordingService extends Service {
|
|||||||
private Handler handler;
|
private Handler handler;
|
||||||
private LocationManagerConnector locationManagerConnector;
|
private LocationManagerConnector locationManagerConnector;
|
||||||
private PeriodicTaskExecutor voiceExecutor;
|
private PeriodicTaskExecutor voiceExecutor;
|
||||||
private PeriodicTaskExecutor splitExecutor;
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
private TrackRecordingServiceNotificationManager notificationManager;
|
private TrackRecordingServiceNotificationManager notificationManager;
|
||||||
private long recordingTrackId;
|
private long recordingTrackId;
|
||||||
@@ -126,14 +124,10 @@ public class TrackRecordingService extends Service {
|
|||||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
voiceExecutor.setMetricUnits(metricUnits);
|
voiceExecutor.setMetricUnits(metricUnits);
|
||||||
splitExecutor.setMetricUnits(metricUnits);
|
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(context));
|
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)) {
|
if (PreferencesUtils.isKey(context, R.string.min_recording_interval_key, key)) {
|
||||||
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
int minRecordingInterval = PreferencesUtils.getMinRecordingInterval(context);
|
||||||
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
if (minRecordingInterval == PreferencesUtils.getMinRecordingIntervalAdaptBatteryLife(context)) {
|
||||||
@@ -219,7 +213,6 @@ public class TrackRecordingService extends Service {
|
|||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
locationManagerConnector = new LocationManagerConnector(this, handler.getLooper());
|
||||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory());
|
||||||
splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory());
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
@@ -272,12 +265,6 @@ public class TrackRecordingService extends Service {
|
|||||||
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
|
// unregister sharedPreferences before shutting down splitExecutor and voiceExecutor
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
|
||||||
try {
|
|
||||||
splitExecutor.shutdown();
|
|
||||||
} finally {
|
|
||||||
splitExecutor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
voiceExecutor.shutdown();
|
voiceExecutor.shutdown();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -566,7 +553,6 @@ public class TrackRecordingService extends Service {
|
|||||||
|
|
||||||
// Restore periodic tasks
|
// Restore periodic tasks
|
||||||
voiceExecutor.restore();
|
voiceExecutor.restore();
|
||||||
splitExecutor.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -653,7 +639,6 @@ public class TrackRecordingService extends Service {
|
|||||||
private void endRecording(boolean trackStopped, long trackId) {
|
private void endRecording(boolean trackStopped, long trackId) {
|
||||||
// Shutdown periodic tasks
|
// Shutdown periodic tasks
|
||||||
voiceExecutor.shutdown();
|
voiceExecutor.shutdown();
|
||||||
splitExecutor.shutdown();
|
|
||||||
|
|
||||||
// Update instance variables
|
// Update instance variables
|
||||||
if (remoteSensorManager != null) {
|
if (remoteSensorManager != null) {
|
||||||
@@ -840,7 +825,6 @@ public class TrackRecordingService extends Service {
|
|||||||
Log.w(TAG, "SQLiteException", e);
|
Log.w(TAG, "SQLiteException", e);
|
||||||
}
|
}
|
||||||
voiceExecutor.update();
|
voiceExecutor.update();
|
||||||
splitExecutor.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-5
@@ -86,11 +86,11 @@ public class BluetoothRemoteSensorManager {
|
|||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||||
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
if (bluetoothConnectionManager != null && PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_heart_rate_key, key)) {
|
||||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
|
||||||
if (address.equals(PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT)) {
|
|
||||||
stop();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||||
if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
|
if (bluetoothConnectionManager.isSameBluetoothDevice(address)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,12 +145,11 @@ public class BluetoothRemoteSensorManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String address = PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
if (PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(context)) {
|
||||||
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
|
||||||
if (PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT.equals(address)) {
|
|
||||||
Log.w(TAG, "No bluetooth address.");
|
Log.w(TAG, "No bluetooth address.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
String address = PreferencesUtils.getBluetoothHeartRateSensorAddress(context);
|
||||||
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
Log.i(TAG, "Connecting to bluetooth address: " + address);
|
||||||
|
|
||||||
BluetoothDevice device;
|
BluetoothDevice device;
|
||||||
|
|||||||
+50
-92
@@ -21,12 +21,8 @@ import android.media.AudioManager;
|
|||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||||
import android.speech.tts.UtteranceProgressListener;
|
import android.speech.tts.UtteranceProgressListener;
|
||||||
import android.telephony.PhoneStateListener;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
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.stats.TripStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TTSUtils;
|
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,17 +42,49 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
/**
|
/**
|
||||||
* The rate at which announcements are spoken.
|
* The rate at which announcements are spoken.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
private static final float TTS_SPEECH_RATE = 0.9f;
|
||||||
static final float TTS_SPEECH_RATE = 0.9f;
|
|
||||||
|
|
||||||
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
|
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private final AudioManager audioManager;
|
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() {
|
private final UtteranceProgressListener utteranceListener = new UtteranceProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onStart(String utteranceId) {
|
public void onStart(String utteranceId) {
|
||||||
|
int result = audioManager.requestAudioFocus(audioFocusChangeListener, TextToSpeech.Engine.DEFAULT_STREAM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||||
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||||
|
Log.w(TAG, "Failed to request audio focus.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,32 +97,16 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(String utteranceId) {
|
public void onError(String utteranceId) {
|
||||||
|
Log.e(TAG, "An error occurred for utteranceId " + utteranceId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private TextToSpeech tts;
|
private TextToSpeech tts;
|
||||||
// Response from TTS after its initialization
|
// Response from TTS after its initialization
|
||||||
private int initStatus = TextToSpeech.ERROR;
|
private int ttsInitStatus = TextToSpeech.ERROR;
|
||||||
|
|
||||||
private boolean ttsReady = false;
|
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) {
|
AnnouncementPeriodicTask(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
@@ -103,16 +114,17 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
|
Log.d(TAG, "Start");
|
||||||
|
|
||||||
if (tts == null) {
|
if (tts == null) {
|
||||||
tts = newTextToSpeech(context, new OnInitListener() {
|
tts = new TextToSpeech(context, new OnInitListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onInit(int status) {
|
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
|
@Override
|
||||||
@@ -129,8 +141,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
*
|
*
|
||||||
* @param tripStatistics the trip statistics
|
* @param tripStatistics the trip statistics
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
private void announce(TripStatistics tripStatistics) {
|
||||||
void announce(TripStatistics tripStatistics) {
|
|
||||||
if (tripStatistics == null) {
|
if (tripStatistics == null) {
|
||||||
Log.e(TAG, "TripStatistics is null.");
|
Log.e(TAG, "TripStatistics is null.");
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +149,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!ttsReady) {
|
if (!ttsReady) {
|
||||||
ttsReady = initStatus == TextToSpeech.SUCCESS;
|
ttsReady = ttsInitStatus == TextToSpeech.SUCCESS;
|
||||||
if (ttsReady) {
|
if (ttsReady) {
|
||||||
onTtsReady();
|
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.");
|
Log.i(TAG, "Speech is not allowed at this time.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -158,21 +169,18 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
listenToPhoneState(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
|
||||||
if (tts != null) {
|
if (tts != null) {
|
||||||
tts.shutdown();
|
tts.shutdown();
|
||||||
tts = null;
|
tts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when TTS is ready.
|
|
||||||
*/
|
|
||||||
private void onTtsReady() {
|
private void onTtsReady() {
|
||||||
Locale locale = Locale.getDefault();
|
Locale locale = Locale.getDefault();
|
||||||
int languageAvailability = tts.isLanguageAvailable(locale);
|
int languageAvailability = tts.isLanguageAvailable(locale);
|
||||||
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA
|
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA || languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||||
|| languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
|
||||||
Log.w(TAG, "Default locale not available, use English.");
|
Log.w(TAG, "Default locale not available, use English.");
|
||||||
locale = Locale.ENGLISH;
|
locale = Locale.ENGLISH;
|
||||||
/*
|
/*
|
||||||
@@ -188,42 +196,12 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
tts.setOnUtteranceProgressListener(utteranceListener);
|
tts.setOnUtteranceProgressListener(utteranceListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Speaks the announcement.
|
|
||||||
*
|
|
||||||
* @param announcement the announcement
|
|
||||||
*/
|
|
||||||
private void speakAnnouncement(String announcement) {
|
private void speakAnnouncement(String announcement) {
|
||||||
int result = audioManager.requestAudioFocus(null, TTSUtils.getTTSStream(), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
||||||
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.
|
|
||||||
*/
|
|
||||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String getAnnouncement(TripStatistics tripStatistics) {
|
||||||
* 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) {
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||||
@@ -256,27 +234,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
getAnnounceTime(tripStatistics.getMovingTime()), rate);
|
getAnnounceTime(tripStatistics.getMovingTime()), rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private String getAnnounceTime(long time) {
|
||||||
* 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) {
|
|
||||||
int[] parts = StringUtils.getTimeParts(time);
|
int[] parts = StringUtils.getTimeParts(time);
|
||||||
String seconds = context.getResources()
|
String seconds = context.getResources()
|
||||||
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,6 +24,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.util.BluetoothUtils;
|
import de.dennisguse.opentracks.util.BluetoothUtils;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preference to select a discoverable Bluetooth LE device.
|
* 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 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) {
|
public BluetoothLePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
}
|
}
|
||||||
@@ -75,6 +78,10 @@ public class BluetoothLePreference extends DialogPreference {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getSummary() {
|
public CharSequence getSummary() {
|
||||||
|
if (getValue() == null || PreferencesUtils.isBluetoothHeartRateSensorAddressDefault(getContext(), getValue())) {
|
||||||
|
return getContext().getString(DEVICE_NONE_RESOURCEID);
|
||||||
|
}
|
||||||
|
|
||||||
return getValue();
|
return getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +149,7 @@ public class BluetoothLePreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String deviceNone = getContext().getString(R.string.sensor_type_value_none);
|
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;
|
selectedEntryIndex = 0;
|
||||||
|
|
||||||
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
|
BluetoothLePreference preference = (BluetoothLePreference) getPreference();
|
||||||
|
|||||||
@@ -167,9 +167,6 @@ public class SettingsActivity extends AppCompatActivity implements ChooseActivit
|
|||||||
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
|
ListPreference voiceFrequency = findPreference(getString(R.string.voice_frequency_key));
|
||||||
voiceFrequency.setEntries(StringUtils.getFrequencyOptions(getActivity(), metricUnits));
|
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));
|
ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key));
|
||||||
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
|
minRecordingInterval.setEntries(PreferenceHelper.getMinRecordingIntervalEntries(getActivity()));
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,8 +32,6 @@ import de.dennisguse.opentracks.R;
|
|||||||
*/
|
*/
|
||||||
public class PreferencesUtils {
|
public class PreferencesUtils {
|
||||||
|
|
||||||
public static final String BLUETOOTH_SENSOR_DEFAULT = "";
|
|
||||||
|
|
||||||
private PreferencesUtils() {
|
private PreferencesUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,12 +249,20 @@ public class PreferencesUtils {
|
|||||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
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) {
|
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) {
|
public static String getBluetoothHeartRateSensorAddress(Context context) {
|
||||||
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, PreferencesUtils.BLUETOOTH_SENSOR_DEFAULT);
|
return PreferencesUtils.getString(context, R.string.settings_sensor_bluetooth_heart_rate_key, getBluetoothHeartRateSensorAddressNone(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldChartShowCadence(Context context) {
|
public static boolean shouldChartShowCadence(Context context) {
|
||||||
@@ -289,6 +295,11 @@ public class PreferencesUtils {
|
|||||||
return getBoolean(context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
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) {
|
public static boolean isShowStatsGradeElevation(Context context) {
|
||||||
final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default);
|
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);
|
return PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION);
|
||||||
@@ -304,11 +315,6 @@ public class PreferencesUtils {
|
|||||||
return PreferencesUtils.getInt(context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
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) {
|
public static int getRecordingDistanceInterval(Context context) {
|
||||||
final int RECORDING_DISTANCE_INTERVAL = Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
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));
|
return PreferencesUtils.getInt(context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||||
|
|||||||
@@ -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).
|
* 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) {
|
if (decimalPlaces < 1) {
|
||||||
return Long.toString(Math.round(value));
|
return Long.toString(Math.round(value));
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ public class StringUtils {
|
|||||||
* @param metricUnits true to use metric unit
|
* @param metricUnits true to use metric unit
|
||||||
* @return the formatted distance (or null) and it's unit as {@link Pair}
|
* @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)) {
|
if (Double.isNaN(distance) || Double.isInfinite(distance)) {
|
||||||
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
|
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
@@ -18,7 +18,6 @@ limitations under the License.
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/marker_detail_waypoint"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@@ -69,33 +68,4 @@ limitations under the License.
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/marker_detail_statistics"
|
|
||||||
style="@style/StatsScrollView">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/marker_detail_statistics_name"
|
|
||||||
style="@style/TextMedium"
|
|
||||||
android:paddingTop="8dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/marker_detail_statistics_location"
|
|
||||||
style="@style/TextSmall" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextSmall"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingBottom="6dp"
|
|
||||||
android:text="@string/marker_detail_statistics_title" />
|
|
||||||
|
|
||||||
<include layout="@layout/stats_content" />
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -41,7 +41,7 @@ limitations under the License.
|
|||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/marker_edit_statistics_name"
|
android:id="@+id/marker_edit_statistics_name"
|
||||||
style="@style/EditNameMarker"
|
style="@style/EditNameMarker"
|
||||||
android:drawableLeft="@drawable/ic_marker_yellow_pushpin"
|
android:drawableLeft="@drawable/ic_marker_blue_pushpin"
|
||||||
android:hint="@string/generic_name"
|
android:hint="@string/generic_name"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:importantForAutofill="no" />
|
android:importantForAutofill="no" />
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ limitations under the License.
|
|||||||
<LinearLayout style="@style/StatsLargeItemContainer">
|
<LinearLayout style="@style/StatsLargeItemContainer">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/stats_sensor_heart_rate_label"
|
|
||||||
style="@style/StatsLargeLabel"
|
style="@style/StatsLargeLabel"
|
||||||
android:text="@string/sensor_state_heart_rate" />
|
android:text="@string/sensor_state_heart_rate" />
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
<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>
|
<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_reset_key" translatable="false">settingsReset</string>
|
||||||
|
|
||||||
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||||
@@ -114,28 +117,6 @@
|
|||||||
<item>@string/auto_resume_track_timeout_always</item>
|
<item>@string/auto_resume_track_timeout_always</item>
|
||||||
</string-array>
|
</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_key" translatable="false">recordingDistanceInterval</string>
|
||||||
<string name="recording_distance_interval_default" translatable="false">10</string>
|
<string name="recording_distance_interval_default" translatable="false">10</string>
|
||||||
<string-array name="recording_distance_interval_values">
|
<string-array name="recording_distance_interval_values">
|
||||||
|
|||||||
@@ -15,4 +15,26 @@
|
|||||||
<item>@string/settings_chart_by_distance</item>
|
<item>@string/settings_chart_by_distance</item>
|
||||||
<item>@string/settings_chart_by_time</item>
|
<item>@string/settings_chart_by_time</item>
|
||||||
</string-array>
|
</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>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1194,6 +1194,13 @@ limitations under the License.
|
|||||||
While recording, show statistics without unlocking the device.
|
While recording, show statistics without unlocking the device.
|
||||||
</string>
|
</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.
|
<string name="settings_recording" translation_description="In the 'Settings' page, the option to change the recording settings.
|
||||||
|
|
||||||
Character limit: ~26 (1 line)">
|
Character limit: ~26 (1 line)">
|
||||||
|
|||||||
@@ -74,18 +74,17 @@ limitations under the License.
|
|||||||
android:key="@string/stats_show_on_lockscreen_while_recording_key"
|
android:key="@string/stats_show_on_lockscreen_while_recording_key"
|
||||||
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
|
android:summary="@string/settings_recording_show_on_lockscreen_while_recording_summary"
|
||||||
android:title="@string/settings_recording_show_on_lockscreen_while_recording_title" />
|
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
|
<ListPreference
|
||||||
android:defaultValue="@string/voice_frequency_default"
|
android:defaultValue="@string/voice_frequency_default"
|
||||||
android:entryValues="@array/frequency_values"
|
android:entryValues="@array/frequency_values"
|
||||||
android:key="@string/voice_frequency_key"
|
android:key="@string/voice_frequency_key"
|
||||||
android:title="@string/menu_voice_frequency"
|
android:title="@string/menu_voice_frequency"
|
||||||
app:useSimpleSummaryProvider="true" />
|
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
|
<ListPreference
|
||||||
android:defaultValue="@string/track_name_default"
|
android:defaultValue="@string/track_name_default"
|
||||||
android:entries="@array/track_name_options"
|
android:entries="@array/track_name_options"
|
||||||
|
|||||||
Reference in New Issue
Block a user