diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java b/myTracks/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java index 497232387..61a994aab 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/content/sensor/SensorDataSet.java @@ -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; } diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java b/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java index e48363551..bb6002cf7 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java @@ -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); diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java b/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java index 6a5f42db6..aab3b19c8 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/fragments/StatsFragment.java @@ -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(); } } diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java b/myTracks/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java index 4c6cdbeab..9ab6a415c 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/services/sensors/SensorManager.java @@ -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); } } diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java b/myTracks/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java index 7faca3f58..e2faf54fa 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/util/StatsUtils.java @@ -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); diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java b/myTracks/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java index 1a50cfff3..95c8244ea 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/util/StringUtils.java @@ -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("\\.$", ""); } diff --git a/myTracks/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java b/myTracks/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java index c8ccba66d..11785313f 100644 --- a/myTracks/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java +++ b/myTracks/src/main/java/com/google/android/apps/mytracks/util/TrackRecordingServiceConnectionUtils.java @@ -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); diff --git a/myTracks/src/main/res/layout/stats_content.xml b/myTracks/src/main/res/layout/stats_content.xml index 29b7d484d..4fc3b6550 100644 --- a/myTracks/src/main/res/layout/stats_content.xml +++ b/myTracks/src/main/res/layout/stats_content.xml @@ -134,4 +134,19 @@ limitations under the License. android:id="@+id/stats_longitude" layout="@layout/stats_small_item" /> + + + + + + \ No newline at end of file diff --git a/myTracks/src/main/res/values/do_not_translate.xml b/myTracks/src/main/res/values/do_not_translate.xml index f0811b8ab..209a6cfec 100644 --- a/myTracks/src/main/res/values/do_not_translate.xml +++ b/myTracks/src/main/res/values/do_not_translate.xml @@ -81,6 +81,8 @@ Google은 이용자에 대한 위치정보의 이용∙제공사실을 위치정 NONE POLAR ZEPHYR + RPM + BPM DYNAMIC FIXED SINGLE diff --git a/myTracks/src/main/res/values/strings.xml b/myTracks/src/main/res/values/strings.xml index 1d7f57865..99a92312d 100644 --- a/myTracks/src/main/res/values/strings.xml +++ b/myTracks/src/main/res/values/strings.xml @@ -1392,7 +1392,7 @@ limitations under the License. %1$d bpm