TrackDetailActivity: show heart rate.

This commit is contained in:
Dennis Guse
2019-07-20 12:11:48 +02:00
parent 09df2dd20f
commit cf0419f27b
10 changed files with 113 additions and 32 deletions
@@ -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;
}
@@ -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);
@@ -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();
}
}
@@ -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("\\.$", "");
}
@@ -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>
+1 -1
View File
@@ -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