mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
TrackDetailActivity: show heart rate.
This commit is contained in:
+8
@@ -64,6 +64,14 @@ public final class SensorDataSet {
|
||||
return creationTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the data recent considering the current time.
|
||||
* @param maxAge the maximal age in milliseconds.
|
||||
*/
|
||||
public boolean isRecent(long maxAge) {
|
||||
return creationTimestamp + maxAge > System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean hasBatteryLevel() {
|
||||
return batteryLevel != DATA_UNAVAILABLE;
|
||||
}
|
||||
|
||||
+2
-4
@@ -248,11 +248,9 @@ public class MarkerDetailFragment extends Fragment {
|
||||
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);
|
||||
ListItemUtils.setTextView(getActivity(), category, StringUtils.getCategory(waypoint.getCategory()), hasPhoto);
|
||||
|
||||
TextView description = getView()
|
||||
.findViewById(R.id.marker_detail_waypoint_description);
|
||||
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);
|
||||
|
||||
+37
-1
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.fragments;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
@@ -36,11 +37,15 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||
import com.google.android.apps.mytracks.content.TrackDataType;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||
import com.google.android.apps.mytracks.util.StatsUtils;
|
||||
import com.google.android.apps.mytracks.util.TrackIconUtils;
|
||||
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -59,8 +64,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
private TrackDataHub trackDataHub;
|
||||
private Handler handlerUpdateUI;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private Location lastLocation = null;
|
||||
private TripStatistics lastTripStatistics = null;
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
private String category = "";
|
||||
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
|
||||
|
||||
@@ -70,12 +78,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
||||
StatsUtils.setTotalTimeValue(getActivity(), System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime());
|
||||
updateSensorDataUI();
|
||||
}
|
||||
|
||||
handlerUpdateUI.postDelayed(this, UnitConversions.ONE_SECOND);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.stats, container, false);
|
||||
@@ -85,7 +96,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
handlerUpdateUI = new Handler();
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||
TrackRecordingServiceConnectionUtils.startConnection(getContext(), trackRecordingServiceConnection);
|
||||
|
||||
Spinner activityTypeIcon = getView().findViewById(R.id.stats_activity_type_icon);
|
||||
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
|
||||
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
|
||||
@@ -125,6 +138,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
handlerUpdateUI.removeCallbacks(updateTotalTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackUpdated(final Track track) {
|
||||
if (isResumed()) {
|
||||
@@ -286,6 +305,22 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackPaused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} {@link com.google.android.apps.mytracks.services.sensors.SensorManager}.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(STATS_FRAGMENT_TAG, "Cannot get the track recording service.");
|
||||
sensorDataSet = null;
|
||||
} else {
|
||||
//TODO sensorState = trackRecordingService.getSensorState();
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
}
|
||||
|
||||
StatsUtils.setSensorData(getActivity(), getActivity(), sensorDataSet, isSelectedTrackRecording());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI.
|
||||
*/
|
||||
@@ -293,5 +328,6 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
|
||||
StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics, trackIconValue);
|
||||
StatsUtils.setLocationValues(activity, activity, null, lastLocation, isSelectedTrackRecording());
|
||||
updateSensorDataUI();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -31,9 +31,10 @@ import java.util.TimerTask;
|
||||
*/
|
||||
public abstract class SensorManager {
|
||||
|
||||
public static final long MAX_SENSOR_DATE_SET_AGE = 5000;
|
||||
public static final long MAX_SENSOR_STATE_AGE = 20000;
|
||||
|
||||
private static final String TAG = SensorManager.class.getSimpleName();
|
||||
private static final long MAX_SENSOR_DATE_SET_AGE = 5000;
|
||||
private static final long MAX_SENSOR_STATE_AGE = 20000;
|
||||
private static final int RETRY_PERIOD = 20000;
|
||||
|
||||
private SensorState sensorState = SensorState.NONE;
|
||||
@@ -132,6 +133,6 @@ public abstract class SensorManager {
|
||||
if (sensorDataSet == null) {
|
||||
return false;
|
||||
}
|
||||
return (System.currentTimeMillis() - sensorDataSet.getCreationTime()) < MAX_SENSOR_DATE_SET_AGE;
|
||||
return sensorDataSet.isRecent(MAX_SENSOR_DATE_SET_AGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.view.View;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.apps.mytracks.content.sensor.SensorDataSet;
|
||||
import com.google.android.apps.mytracks.services.sensors.SensorManager;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
@@ -51,8 +53,7 @@ public class StatsUtils {
|
||||
* @param location the location
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
public static void setLocationValues(
|
||||
Context context, Activity activity, View view, Location location, boolean isRecording) {
|
||||
public static void setLocationValues(Context context, Activity activity, View view, Location location, boolean isRecording) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
|
||||
@@ -61,29 +62,22 @@ public class StatsUtils {
|
||||
speed.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
if (isRecording) {
|
||||
double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed()
|
||||
: Double.NaN;
|
||||
setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits,
|
||||
reportSpeed);
|
||||
double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed() : Double.NaN;
|
||||
setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits, reportSpeed);
|
||||
}
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = PreferencesUtils.getBoolean(
|
||||
context, R.string.stats_show_grade_elevation_key,
|
||||
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording;
|
||||
boolean showGradeElevation = PreferencesUtils.getBoolean(context, R.string.stats_show_grade_elevation_key, PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording;
|
||||
View elevation = getView(activity, view, R.id.stats_elevation);
|
||||
elevation.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showGradeElevation) {
|
||||
double altitude = location != null && location.hasAltitude() ? location.getAltitude()
|
||||
: Double.NaN;
|
||||
double altitude = location != null && location.hasAltitude() ? location.getAltitude() : Double.NaN;
|
||||
setElevationValue(context, elevation, -1, altitude, metricUnits);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
boolean showCoordinate = PreferencesUtils.getBoolean(
|
||||
context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT)
|
||||
&& isRecording;
|
||||
boolean showCoordinate = isRecording && PreferencesUtils.getBoolean(context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
|
||||
View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator);
|
||||
View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container);
|
||||
|
||||
@@ -95,10 +89,19 @@ public class StatsUtils {
|
||||
if (showCoordinate) {
|
||||
double latitude = location != null ? location.getLatitude() : Double.NaN;
|
||||
double longitude = location != null ? location.getLongitude() : Double.NaN;
|
||||
setCoordinateValue(
|
||||
context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude);
|
||||
setCoordinateValue(context, getView(activity, view, R.id.stats_longitude),
|
||||
R.string.stats_longitude, longitude);
|
||||
setCoordinateValue(context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude);
|
||||
setCoordinateValue(context, getView(activity, view, R.id.stats_longitude), R.string.stats_longitude, longitude);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSensorData(Context context, Activity activity, SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
{
|
||||
// SensorDataSet: heart rate
|
||||
View view = getView(activity, null, R.id.stats_sensor_heart_rate_value);
|
||||
view.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
setHeartRateValue(context, view, sensorDataSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +112,7 @@ public class StatsUtils {
|
||||
* @param totalTime the total time
|
||||
*/
|
||||
public static void setTotalTimeValue(Activity activity, long totalTime) {
|
||||
setTimeValue(activity, activity.findViewById(R.id.stats_total_time), R.string.stats_total_time,
|
||||
totalTime);
|
||||
setTimeValue(activity, activity.findViewById(R.id.stats_total_time), R.string.stats_total_time, totalTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,6 +283,21 @@ public class StatsUtils {
|
||||
setItem(context, view, labelId, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a heart rate value (sensor)
|
||||
*
|
||||
* @param context the context
|
||||
* @param view the containing view
|
||||
* @param sensorDataSet the sensorDataSet
|
||||
*/
|
||||
private static void setHeartRateValue(Context context, View view, SensorDataSet sensorDataSet) {
|
||||
String heartRate = context.getString(R.string.value_unknown);
|
||||
if (sensorDataSet != null && sensorDataSet.hasHeartRate() && sensorDataSet.isRecent(SensorManager.MAX_SENSOR_DATE_SET_AGE)) {
|
||||
heartRate = StringUtils.formatDecimal(sensorDataSet.getHeartRate(), 0);
|
||||
}
|
||||
setItem(context, view, R.string.sensor_state_heart_rate, heartRate, context.getString(R.string.sensor_unit_beats_per_minute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item.
|
||||
*
|
||||
@@ -328,6 +345,7 @@ public class StatsUtils {
|
||||
* @param view the containing view
|
||||
* @param id the id
|
||||
*/
|
||||
//TODO What is the difference between the two cases? Is it (still) necessary?
|
||||
private static View getView(Activity activity, View view, int id) {
|
||||
if (activity != null) {
|
||||
return activity.findViewById(id);
|
||||
|
||||
@@ -152,7 +152,11 @@ public class StringUtils {
|
||||
}
|
||||
|
||||
public static String formatDecimal(double value) {
|
||||
String result = String.format(Locale.getDefault(), "%1$,.2f", value);
|
||||
return StringUtils.formatDecimal(value, 2);
|
||||
}
|
||||
|
||||
public static String formatDecimal(double value, int decimalPlaces) {
|
||||
String result = String.format(Locale.getDefault(), "%1$,." + decimalPlaces +"f", value);
|
||||
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -133,8 +133,7 @@ public class TrackRecordingServiceConnectionUtils {
|
||||
* @param trackRecordingServiceConnection the track recording service
|
||||
* connection
|
||||
*/
|
||||
public static void startConnection(
|
||||
Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
|
||||
public static void startConnection(Context context, TrackRecordingServiceConnection trackRecordingServiceConnection) {
|
||||
trackRecordingServiceConnection.bindIfStarted();
|
||||
if (!isRecordingServiceRunning(context)) {
|
||||
resetRecordingState(context);
|
||||
|
||||
@@ -134,4 +134,19 @@ limitations under the License.
|
||||
android:id="@+id/stats_longitude"
|
||||
layout="@layout/stats_small_item" />
|
||||
</LinearLayout>
|
||||
<!-- Sensor data-->
|
||||
<View
|
||||
android:id="@+id/stats_sensor_separator"
|
||||
style="@style/StatsHorizontalLine" />
|
||||
<LinearLayout
|
||||
android:id="@+id/stats_sensor_container"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent" >
|
||||
<include
|
||||
android:id="@+id/stats_sensor_heart_rate_value"
|
||||
layout="@layout/stats_small_item" />
|
||||
<include
|
||||
android:id="@+id/stats_sensor_PLACEHOLDER"
|
||||
layout="@layout/stats_small_item" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -81,6 +81,8 @@ Google은 이용자에 대한 위치정보의 이용∙제공사실을 위치정
|
||||
<string name="sensor_type_value_none">NONE</string>
|
||||
<string name="sensor_type_value_polar">POLAR</string>
|
||||
<string name="sensor_type_value_zephyr">ZEPHYR</string>
|
||||
<string name="sensor_unit_rounds_per_minute">RPM</string>
|
||||
<string name="sensor_unit_beats_per_minute">BPM</string>
|
||||
<string name="settings_map_track_color_mode_dynamic_value">DYNAMIC</string>
|
||||
<string name="settings_map_track_color_mode_fixed_value">FIXED</string>
|
||||
<string name="settings_map_track_color_mode_single_value">SINGLE</string>
|
||||
|
||||
@@ -1392,7 +1392,7 @@ limitations under the License.
|
||||
</string>
|
||||
<string name="sensor_state_heart_rate_value"
|
||||
translation_description="In the 'Sensor state' page, the value for the 'Heart rate'. E.g.,
|
||||
'Hear rate: 100 bpm'.
|
||||
'Heart rate: 100 bpm'.
|
||||
|
||||
%1$d a number. E.g., '100'.">
|
||||
%1$d bpm
|
||||
|
||||
Reference in New Issue
Block a user