diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackEditActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackEditActivity.java
index d6a23d063..c3458509e 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackEditActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackEditActivity.java
@@ -182,32 +182,34 @@ public class TrackEditActivity extends AbstractMyTracksActivity
Button save = (Button) findViewById(R.id.track_edit_save);
save.setOnClickListener(new View.OnClickListener() {
- @Override
+ @Override
public void onClick(View v) {
- String category = activityType.getText().toString();
track.setName(name.getText().toString());
+
+ String category = activityType.getText().toString();
+ boolean newCategory = !category.equals(track.getCategory());
+ boolean isRecording = trackId
+ == PreferencesUtils.getLong(TrackEditActivity.this, R.string.recording_track_id_key);
+
+ if (newCategory && !isRecording) {
+ // Update calorie
+ double calorie = CalorieUtils.getTrackCalorie(TrackEditActivity.this, track, -1L);
+ track.getTripStatistics().setCalorie(calorie);
+ }
+
+ track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(TrackEditActivity.this, category));
track.setDescription(description.getText().toString());
track.setModifiedTime(System.currentTimeMillis());
- if (!category.equals(track.getCategory())) {
- track.setCategory(category);
+ myTracksProviderUtils.updateTrack(track);
- // If edit recording track.
- if (track.getId() == PreferencesUtils.getLong(getApplicationContext(),
- R.string.recording_track_id_key)) {
-
- // Update data without new calorie.
- myTracksProviderUtils.updateTrack(track);
- TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
- } else {
- CalorieUtils.updateCalorie(getApplicationContext(), -1L, track);
-
- // Update data with new calorie.
- myTracksProviderUtils.updateTrack(track);
- }
+ if (newCategory && isRecording) {
+ // Update calorie through track recording service
+ TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
}
- boolean driveSync = PreferencesUtils.getBoolean(TrackEditActivity.this,
- R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
+
+ boolean driveSync = PreferencesUtils.getBoolean(
+ TrackEditActivity.this, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
if (driveSync) {
PreferencesUtils.addToList(TrackEditActivity.this, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
index 644c1e65c..5e39c3924 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
@@ -1006,7 +1006,7 @@ public class TrackRecordingService extends Service {
Uri uri = myTracksProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, true,
- CalorieUtils.getActivityType(context, track.getId()), PreferencesUtils.getInt(context,
+ CalorieUtils.getActivityType(context, track.getCategory()), PreferencesUtils.getInt(context,
R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT));
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
@@ -1359,37 +1359,36 @@ public class TrackRecordingService extends Service {
return;
}
executorService.submit(new Runnable() {
- @Override
+ @Override
public void run() {
- // Update track statistics.
- long trackId = PreferencesUtils.getLong(getApplicationContext(),
- R.string.recording_track_id_key);
- if (trackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
+ if (!isRecording()) {
+ Log.w(TAG, "Ignore updateCalorie. Not recording.");
return;
}
- Track currentTrack = myTracksProviderUtils.getTrack(trackId);
- if (currentTrack == null) {
+
+ Track track = myTracksProviderUtils.getTrack(recordingTrackId);
+ if (track == null) {
+ Log.w(TAG, "Ignore updateCalorie. No track.");
return;
}
- double newCalorie = CalorieUtils.updateCalorie(getApplicationContext(), -1L, currentTrack);
- trackTripStatisticsUpdater.updateCalorie(newCalorie);
- // Update to database.
- myTracksProviderUtils.updateTrack(currentTrack);
+ double calorie = CalorieUtils.getTrackCalorie(context, track, -1L);
- // Update marker statistics.
- Waypoint lastWayPoint = myTracksProviderUtils.getLastWaypoint(trackId,
- WaypointType.WAYPOINT);
- if (lastWayPoint == null) {
- return;
- }
- long lastWayPoint_pointId = myTracksProviderUtils.getTrackPointId(trackId,
- lastWayPoint.getLocation());
- if (lastWayPoint_pointId > 0) {
- newCalorie = CalorieUtils.updateCalorie(getApplicationContext(), lastWayPoint_pointId,
- currentTrack);
- }
- markerTripStatisticsUpdater.updateCalorie(newCalorie);
+ // Update to database
+ track.getTripStatistics().setCalorie(calorie);
+ myTracksProviderUtils.updateTrack(track);
+
+ // Update track statistics
+ trackTripStatisticsUpdater.updateCalorie(calorie);
+
+ // Update marker statistics
+ Waypoint wayPoint = myTracksProviderUtils.getLastWaypoint(
+ recordingTrackId, WaypointType.STATISTICS);
+ long trackPointId = wayPoint != null ? myTracksProviderUtils.getTrackPointId(
+ recordingTrackId, wayPoint.getLocation())
+ : -1L;
+ calorie = CalorieUtils.getTrackCalorie(context, track, trackPointId);
+ markerTripStatisticsUpdater.updateCalorie(calorie);
}
});
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java
index f51748611..1e5fe5fde 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/CalorieUtils.java
@@ -26,34 +26,44 @@ import android.content.Context;
import android.location.Location;
/**
- * Utils to calculate calories.
+ * Utilities to calculate calories.
*
* @author youtaol
*/
public class CalorieUtils {
+ /**
+ * Activity types.
+ */
+ public enum ActivityType {
+ CYCLING, FOOT, INVALID
+ }
+
private CalorieUtils() {}
/**
- * Resting VO2 is constant for everyone and is equal to 3.5 milliliters per
- * kilogram of body weight per minute.
+ * Resting VO2 is 3.5 milliliters per kilogram of body weight per minute
+ * (ml/kg/min). The resting VO2 is the same for everyone.
*/
private static final double RESTING_VO2 = 3.5;
/**
- * Ratio of change Liter to kcal. In kcal per Liter.
+ * Ratio to convert liter to kcal.
*/
- private static final double L_TO_KCAL = 5;
-
- private static final double earthGravity = 9.8;
+ private static final double L_TO_KCAL = 5.0;
/**
- * Lumped constant for all frictional losses (tires, bearings, chain)
+ * Standard gravity in meters per second squared (m/s^2).
+ */
+ private static final double EARTH_GRAVITY = 9.80665;
+
+ /**
+ * Lumped constant for all frictional losses (tires, bearings, chain).
*/
private static final double K1 = 0.0053;
/**
- * Lumped constant for aerodynamic drag
+ * Lumped constant for aerodynamic drag.
*/
private static final double K2 = 0.185;
@@ -61,7 +71,7 @@ public class CalorieUtils {
* Critical speed running in meters per minute. Converts 4.5 miles per hour to
* meters per minute. 4 miles per hour is regarded as the max speed of walking
* and 5 miles per hour is regarded as the minimal speed of running, so we use
- * the 4.5 miles per hour as the critical seed. Reference
*/
@@ -69,22 +79,194 @@ public class CalorieUtils {
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
- public enum ActivityType {
- CYCLING, FOOT, INVALID
+ /**
+ * Gets the activity type.
+ *
+ * @param context the context
+ * @param activityType the activity type
+ */
+ public static ActivityType getActivityType(Context context, String activityType) {
+ if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)
+ || TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
+ return ActivityType.FOOT;
+ } else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.BIKE)) {
+ return ActivityType.CYCLING;
+ }
+ return ActivityType.INVALID;
}
/**
- * Calculates the calorie of walking. This equation is appropriate for fairly
- * slow speed ranges—from 1.9 to approximately 4 miles per hour (mph).
+ * Calculates the track calorie in kcal.
*
- * @param speed is calculated in meters per second (m/s)
- * @param grade
- * @return the VO2 value in ml/kg/min.
+ * @param context the context
+ * @param track the track
+ * @param startTrackPointId the starting track point id. -1L to calculate the
+ * entire track
+ */
+ public static double getTrackCalorie(Context context, Track track, long startTrackPointId) {
+ ActivityType activityType = getActivityType(context, track.getCategory());
+ if (activityType == ActivityType.INVALID) {
+ return 0.0;
+ }
+
+ MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
+ LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
+ track.getId(), startTrackPointId, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
+ track.getTripStatistics().getStartTime());
+ int recordingDistanceInterval = PreferencesUtils.getInt(context,
+ R.string.recording_distance_interval_key,
+ PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
+ int statsWeight = PreferencesUtils.getInt(
+ context, R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT);
+ while (iterator.hasNext()) {
+ tripStatisticsUpdater.addLocation(
+ iterator.next(), recordingDistanceInterval, true, activityType, statsWeight);
+ }
+ return tripStatisticsUpdater.getTripStatistics().getCalorie();
+ }
+
+ /**
+ * Gets the calorie in kcal between two locations.
+ *
+ * @param start the start location
+ * @param stop the stop location
+ * @param grade the grade
+ * @param weight the weight in kilogram. For cycling, weight of the rider plus
+ * bike. For foot, weight of the user
+ * @param activityType the activity type
+ */
+ public static double getCalorie(
+ Location start, Location stop, double grade, int weight, ActivityType activityType) {
+ if (activityType == ActivityType.INVALID) {
+ return 0.0;
+ }
+ return ActivityType.CYCLING == activityType ? getCyclingCalorie(start, stop, grade, weight)
+ : getFootCalorie(start, stop, grade, weight);
+ }
+
+ /**
+ * Gets the cycling calorie in kcal between two locations.
+ *
+ * @param start the start location
+ * @param stop the stop location
+ * @param grade the grade
+ * @param weight the weight in kilogram of the rider plus bike
+ */
+ @VisibleForTesting
+ static double getCyclingCalorie(Location start, Location stop, double grade, int weight) {
+ // Gets duration in seconds
+ double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S;
+ // Get speed in meters per second
+ double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
+
+ if (grade < 0) {
+ grade = 0.0;
+ }
+ return getCyclingCalorie(speed, grade, weight, duration);
+ }
+
+ /**
+ * Gets the cycling calorie in kcal using the following equation:
+ * P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
+ *