merge heads

This commit is contained in:
Jimmy Shih
2013-11-20 10:36:47 -08:00
parent 45909e70b6
commit 63c1330f50
68 changed files with 1487 additions and 1176 deletions
@@ -18,11 +18,13 @@ package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
/**
@@ -36,7 +38,7 @@ public class CalorieUtils {
* Activity types.
*/
public enum ActivityType {
CYCLING, FOOT, INVALID
CYCLING, RUNNING, WALKING, INVALID
}
private CalorieUtils() {}
@@ -47,15 +49,10 @@ public class CalorieUtils {
*/
private static final double RESTING_VO2 = 3.5;
/**
* Ratio to convert liter to kcal.
*/
private static final double L_TO_KCAL = 5.0;
/**
* Standard gravity in meters per second squared (m/s^2).
*/
private static final double EARTH_GRAVITY = 9.80665;
private static final double EARTH_GRAVITY = 9.81;
/**
* Lumped constant for all frictional losses (tires, bearings, chain).
@@ -63,22 +60,10 @@ public class CalorieUtils {
private static final double K1 = 0.0053;
/**
* Lumped constant for aerodynamic drag.
* Lumped constant for aerodynamic drag (kg/m)
*/
private static final double K2 = 0.185;
/**
* 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
* 4.5 miles per hour as the critical speed. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
@VisibleForTesting
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
/**
* Gets the activity type.
*
@@ -86,9 +71,13 @@ public class CalorieUtils {
* @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;
if (activityType == null || activityType.equals("")) {
return ActivityType.INVALID;
}
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)) {
return ActivityType.WALKING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.RUNNING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.BIKE)) {
return ActivityType.CYCLING;
}
@@ -96,34 +85,79 @@ public class CalorieUtils {
}
/**
* Calculates the track calorie in kcal.
* Updates calories for a track and its waypoints.
*
* @param context the context
* @param track the track
* @param startTrackPointId the starting track point id. -1L to calculate the
* entire track
* @return an array of two doubles, first is the track calorie, second is the
* last statistics waypoint calorie
*/
public static double getTrackCalorie(Context context, Track track, long startTrackPointId) {
public static double[] updateTrackCalorie(Context context, Track track) {
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
ActivityType activityType = getActivityType(context, track.getCategory());
if (activityType == ActivityType.INVALID) {
return 0.0;
clearCalorie(myTracksProviderUtils, track);
return new double[] { 0.0, 0.0 };
}
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), startTrackPointId, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
TripStatisticsUpdater trackTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = 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);
double weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
LocationIterator locationIterator = null;
Cursor cursor = null;
try {
Waypoint waypoint = null;
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
waypoint = getNextStatisticsWaypoint(myTracksProviderUtils, cursor);
}
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
if (waypoint != null && waypoint.getLocation().getTime() == location.getTime()
&& waypoint.getLocation().getLatitude() == location.getLatitude()
&& waypoint.getLocation().getLongitude() == location.getLongitude()) {
waypoint.getTripStatistics()
.setCalorie(markerTripStatisticsUpdater.getTripStatistics().getCalorie());
myTracksProviderUtils.updateWaypoint(waypoint);
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypoint = getNextStatisticsWaypoint(myTracksProviderUtils, cursor);
}
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
if (cursor != null) {
cursor.close();
}
}
return tripStatisticsUpdater.getTripStatistics().getCalorie();
double trackCalorie = trackTripStatisticsUpdater.getTripStatistics().getCalorie();
track.getTripStatistics().setCalorie(trackCalorie);
myTracksProviderUtils.updateTrack(track);
return new double[] {
trackCalorie, markerTripStatisticsUpdater.getTripStatistics().getCalorie() };
}
/**
@@ -137,141 +171,137 @@ public class CalorieUtils {
* @param activityType the activity type
*/
public static double getCalorie(
Location start, Location stop, double grade, int weight, ActivityType activityType) {
Location start, Location stop, double grade, double 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: <br>
* P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
* <ul>
* <li>P - Power in watt (Joule/second)</li>
* <li>g - gravity, using 9.80665 meter/second^2</li>
* <li>m - mass of the rider plus bike in kilogram</li>
* <li>s - surface grade</li>
* <li>Vg - ground relative speed in meter/second</li>
* <li>Va - air relative speed. We assume the air speed is zero, so Va is
* equal to Vg.</li>
* <li>K1 - a constant which represents frictional losses, using 0.0053.</li>
* <li>K2 - a constant representing aerodynamic drag, using 0.185.</li>
* <li></li>
* </ul>
*
* @param speed the speed in meters per second
* @param grade the grade
* @param weight weight in kilogram of the rider plus bike
* @param duration the duration in seconds
*/
@VisibleForTesting
static double getCyclingCalorie(double speed, double grade, int weight, double duration) {
// Get the power in watt (Joule/second)
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// Get the calories in kcal
return power * duration * UnitConversions.J_TO_KCAL;
}
/**
* Gets the foot calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight of the user in kilogram
*/
@VisibleForTesting
static double getFootCalorie(Location start, Location stop, double grade, int weight) {
// Get speed in meters per second
double averageSpeed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Get VO2 in mL/kg/min
double vo2 = getFootVo2(averageSpeed, grade);
// Duration in minutes
// Speed in m/s
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Duration in min
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN;
/*
* Get the calorie. The unit of calorie is kcal which is came from
* (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * L_TO_KCAL;
}
/**
* Gets the foot VO2 value in ml/kg/min.
*
* @param speed the speed in meters per second
* @param grade the grade
*/
@VisibleForTesting
static double getFootVo2(double speed, double grade) {
if (grade < 0) {
grade = 0.0;
if (activityType == ActivityType.CYCLING) {
/*
* Power in watt (Joule/second). See
* http://en.wikipedia.org/wiki/Bicycle_performance.
*/
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// WorkRate in kgm/min
double workRate = power * UnitConversions.W_TO_KGM;
/*
* VO2 in kgm/min/kg 1.8 = oxygen cost of producing 1 kgm/min of power
* output. 7 = oxygen cost of unloaded cycling plus resting oxygen
* consumption
*/
double vo2 = (1.8 * workRate / weight) + 7;
// Calorie in kcal
return vo2 * duration * weight * UnitConversions.KGM_TO_KCAL;
} else {
double vo2 = activityType == ActivityType.RUNNING ? getRunningVo2(speed, grade)
: getWalkingVo2(speed, grade);
/*
* Calorie in kcal (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * UnitConversions.L_TO_KCAL;
}
return speed > CRTICAL_SPEED_RUNNING ? getHighSpeedFootVo2(speed, grade)
: getLowSpeedFootVo2(speed, grade);
}
/**
* Gets the high speed foot VO2 in ml/kg/min. This equation is appropriate for
* speeds greater than 4.5 mph like running.
* Clears calorie in the track and its waypoints.
*
* @param speed the speed in meters per second
* @param grade the grade
* @param myTracksProviderUtils the my tracks provider utils
* @param track the track
*/
@VisibleForTesting
static double getHighSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.2 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while running (horizontally). 0.9 means oxygen cost per meter of
* moving total body mass against gravity (vertically).
*/
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
private static void clearCalorie(MyTracksProviderUtils myTracksProviderUtils, Track track) {
track.getTripStatistics().setCalorie(0);
myTracksProviderUtils.updateTrack(track);
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
waypoint.getTripStatistics().setCalorie(0);
myTracksProviderUtils.updateWaypoint(waypoint);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Gets the low speed foot VO2 in ml/kg/min. This equation is appropriate for
* speed less than 4.5 mph like walking.
* Gets the next statistics waypoint from a cursor.
*
* @param speed the speed in meters per second
* @param myTracksProviderUtils the my tracks provider utils
* @param cursor the cursor
*/
private static Waypoint getNextStatisticsWaypoint(
MyTracksProviderUtils myTracksProviderUtils, Cursor cursor) {
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) {
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
if (waypoint.getType() == WaypointType.STATISTICS) {
return waypoint;
}
}
return null;
}
/**
* Gets the running VO2 in ml/kg/min. This equation is appropriate for speeds
* greater than 5 mi/hr (or 3 mi/hr or greater if the subject is truly
* jogging).
*
* @param speed the speed in m/s
* @param grade the grade
*/
@VisibleForTesting
static double getLowSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
private static double getRunningVo2(double speed, double grade) {
// Change from m/s to m/min
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.1 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while walking (horizontally). 1.8 means oxygen cost per meter of
* moving total body mass against gravity (vertically).
* 0.2 = oxygen cost per meter of moving each kg of body weight while
* running (horizontally). 0.9 = oxygen cost per meter of moving total body
* mass against gravity (vertically).
*/
return 0.1 * speed + 1.8 * speed * Math.abs(grade) + RESTING_VO2;
return 0.2 * speed + 0.9 * speed * grade + RESTING_VO2;
}
/**
* Gets the walking VO2 in ml/kg/min. This equation is appropriate for speed
* from 1.9 to 4 mi/hr.
*
* @param speed the speed in m/s
* @param grade the grade
*/
private static double getWalkingVo2(double speed, double grade) {
// Change from m/s to m/min
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.1 = oxygen cost per meter of moving each kilogram (kg) of body weight
* while walking (horizontally). 1.8 = oxygen cost per meter of moving total
* body mass against gravity (vertically).
*/
return 0.1 * speed + 1.8 * speed * grade + RESTING_VO2;
}
}
@@ -32,7 +32,9 @@ public class LocationUtils {
private static final String TAG = LocationUtils.class.getSimpleName();
private static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute
// 1 minute in milliseconds
private static final long MAX_LOCATION_AGE_MS = (long) (UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
private LocationUtils() {}
@@ -27,6 +27,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.text.TextUtils;
import android.util.Log;
/**
* Utilities to access preferences stored in {@link SharedPreferences}.
@@ -106,8 +107,6 @@ public class PreferencesUtils {
public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false;
public static final String STATS_UNITS_DEFAULT = "METRIC";
public static final boolean STATS_SHOW_CALORIE_DEFAULT = false;
public static final int STATS_WEIGHT_DEFAULT = 65; // in kilogram
// Track color
public static final String TRACK_COLOR_MODE_DEFAULT = "SINGLE";
@@ -124,6 +123,10 @@ public class PreferencesUtils {
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
public static final int VOICE_FREQUENCY_DEFAULT = 0;
public static final float WEIGHT_DEFAULT = 65.0f; // in kilogram
private static final String TAG = PreferencesUtils.class.getSimpleName();
private PreferencesUtils() {}
/**
@@ -194,6 +197,35 @@ public class PreferencesUtils {
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a float preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
public static float getFloat(Context context, int keyId, float defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getFloat(getKey(context, keyId), defaultValue);
}
/**
* Sets a float preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
@SuppressLint("CommitPrefEdits")
public static void setFloat(Context context, int keyId, float value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putFloat(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a long preference value.
*
@@ -304,4 +336,37 @@ public class PreferencesUtils {
}
setString(context, keyId, list + ";" + value);
}
/**
* Stores the weight value, always in metric units.
*
* @param displayValue the display value
*/
public static void storeWeightValue(Context context, String displayValue) {
double value;
try {
value = Double.parseDouble(displayValue);
if (!PreferencesUtils.isMetricUnits(context)) {
value = value * UnitConversions.LB_TO_KG;
}
} catch (NumberFormatException e) {
Log.e(TAG, "invalid value " + displayValue);
value = PreferencesUtils.WEIGHT_DEFAULT;
}
PreferencesUtils.setFloat(context, R.string.weight_key, (float) value);
}
/**
* Gets the weight display value.
*
* @param context the context
*/
public static double getWeightDisplayValue(Context context) {
double value = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
if (!PreferencesUtils.isMetricUnits(context)) {
value = value * UnitConversions.KG_TO_LB;
}
return value;
}
}
@@ -16,8 +16,6 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.maps.mytracks.R;
@@ -26,6 +24,7 @@ import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.Locale;
@@ -40,9 +39,8 @@ public class StatsUtils {
private static final String COORDINATE_DEGREE = "\u00B0";
private static final String GRADE_PERCENTAGE = "%";
private static final String ELEVATION_FORMAT = "%1$.2f";
private static final String GRADE_FORMAT = "%1$d";
private static final String CALORIES_FORMAT = "%1$.0f";
private static final String CALORIES_FORMAT = "%1$,.0f";
private StatsUtils() {}
@@ -63,44 +61,48 @@ public class StatsUtils {
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
// Set speed/pace
double speed = isRecording && location != null && location.hasSpeed() ? location.getSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_speed), R.string.stats_speed,
R.string.stats_pace, speed, metricUnits, reportSpeed);
View speed = getView(activity, view, R.id.stats_speed);
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);
}
// Set elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context,
R.string.stats_show_grade_elevation_key,
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
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 && isRecording) {
if (showGradeElevation) {
double altitude = location != null && location.hasAltitude() ? location.getAltitude()
: Double.NaN;
elevation.setVisibility(View.VISIBLE);
setElevationValue(context, elevation, -1, altitude, metricUnits);
} else {
elevation.setVisibility(View.GONE);
}
// Set coordinate
boolean showCoordinate = PreferencesUtils.getBoolean(context,
R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
View coordinateHorizontalLine = getView(activity, view, R.id.stats_coordinate_horizontal_line);
boolean showCoordinate = PreferencesUtils.getBoolean(
context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT)
&& isRecording;
View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator);
View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container);
if (showCoordinate && isRecording) {
if (coordinateSeparator != null) {
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
}
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = location != null ? location.getLatitude() : Double.NaN;
double longitude = location != null ? location.getLongitude() : Double.NaN;
coordinateHorizontalLine.setVisibility(View.VISIBLE);
coordinateContainer.setVisibility(View.VISIBLE);
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);
} else {
coordinateHorizontalLine.setVisibility(View.GONE);
coordinateContainer.setVisibility(View.GONE);
}
}
@@ -124,12 +126,12 @@ public class StatsUtils {
* @param view the containing view for finding views. If null, the activity
* cannot be null
* @param tripStatistics the trip statistics
* @param trackId the id of track, which is used to set calorie value. Does
* not handle the calorie If the value is
* {@link PreferencesUtils#RECORDING_TRACK_ID_DEFAULT}
* @param activityType the activity type
* @param trackIconValue the track icon value or null to hide the track icon
* spinner
*/
public static void setTripStatisticsValues(
Context context, Activity activity, View view, TripStatistics tripStatistics, long trackId) {
public static void setTripStatisticsValues(Context context, Activity activity, View view,
TripStatistics tripStatistics, ActivityType activityType, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
@@ -138,43 +140,54 @@ public class StatsUtils {
setDistanceValue(
context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits);
// Set calorie
double calorie = tripStatistics == null || activityType == ActivityType.INVALID ? Double.NaN
: tripStatistics.getCalorie();
setCalorie(context, getView(activity, view, R.id.stats_calorie), calorie);
Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon);
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
}
// Set total time
setTimeValue(context, getView(activity, view, R.id.stats_total_time), R.string.stats_total_time,
tripStatistics != null ? tripStatistics.getTotalTime() : -1L);
// Set moving time
setTimeValue(context, getView(activity, view, R.id.stats_moving_time),
R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
// Set average speed/pace
double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_speed),
R.string.stats_average_speed, R.string.stats_average_pace, averageSpeed, metricUnits,
reportSpeed);
// Set moving time
setTimeValue(context, getView(activity, view, R.id.stats_moving_time),
R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
// Set average moving speed/pace
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_moving_time),
R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed,
metricUnits, reportSpeed);
// Set max speed/pace
double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
setSpeed(context, getView(activity, view, R.id.stats_max_speed), R.string.stats_max_speed,
R.string.stats_fastest_pace, maxSpeed, metricUnits, reportSpeed);
// Set average moving speed/pace
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_moving_speed),
R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed,
metricUnits, reportSpeed);
// Set grade/elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context,
R.string.stats_show_grade_elevation_key,
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
View gradeElevationHorizontalLine = getView(
activity, view, R.id.stats_grade_elevation_horizontal_line);
View gradeElevationSeparator = getView(activity, view, R.id.stats_grade_elevation_separator);
View gradeElevationContainer = getView(activity, view, R.id.stats_grade_elevation_container);
gradeElevationSeparator.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
gradeElevationHorizontalLine.setVisibility(View.VISIBLE);
gradeElevationContainer.setVisibility(View.VISIBLE);
// Set grade
double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade();
double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade();
@@ -194,33 +207,6 @@ public class StatsUtils {
R.string.stats_min, minElevation, metricUnits);
setElevationValue(context, getView(activity, view, R.id.stats_elevation_max),
R.string.stats_max, maxElevation, metricUnits);
} else {
gradeElevationHorizontalLine.setVisibility(View.GONE);
gradeElevationContainer.setVisibility(View.GONE);
}
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
// Set calories
boolean showCalorie = PreferencesUtils.getBoolean(
context, R.string.stats_show_calorie_key, PreferencesUtils.STATS_SHOW_CALORIE_DEFAULT);
View calorieHorizontalLine = getView(activity, view, R.id.stats_calorie_horizontal_line);
View calorieContainer = getView(activity, view, R.id.stats_calorie_container);
if (showCalorie) {
calorieHorizontalLine.setVisibility(View.VISIBLE);
calorieContainer.setVisibility(View.VISIBLE);
double calories = Double.NaN;
if (tripStatistics != null) {
calories = tripStatistics.getCalorie();
}
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
Track track = myTracksProviderUtils.getTrack(trackId);
ActivityType activityType = track == null ? ActivityType.INVALID
: CalorieUtils.getActivityType(context, track.getCategory());
setCalorie(context, getView(activity, view, R.id.stats_calorie), activityType, calories);
} else {
calorieHorizontalLine.setVisibility(View.GONE);
calorieContainer.setVisibility(View.GONE);
}
}
}
@@ -241,6 +227,19 @@ public class StatsUtils {
setItem(context, view, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]);
}
/**
* Sets calorie.
*
* @param context the context
* @param view the containing view
* @param calorie the value of calorie
*/
private static void setCalorie(Context context, View view, double calorie) {
String value = Double.isNaN(calorie) ? null
: String.format(Locale.getDefault(), CALORIES_FORMAT, calorie);
setItem(context, view, R.string.stats_calorie, value, context.getString(R.string.unit_calorie));
}
/**
* Sets distance value.
*
@@ -286,11 +285,11 @@ public class StatsUtils {
unit = null;
} else {
if (metricUnits) {
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_meter);
} else {
elevation *= UnitConversions.M_TO_FT;
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_feet);
}
}
@@ -322,28 +321,8 @@ public class StatsUtils {
private static void setCoordinateValue(
Context context, View view, int labelId, double coordinate) {
String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null
: Location.convert(coordinate, Location.FORMAT_DEGREES);
setItem(context, view, labelId, value, COORDINATE_DEGREE);
}
/**
* Sets calorie.
*
* @param context the context
* @param view the containing view
* @param activityType the activity type
* @param calorie the value of calorie
*/
private static void setCalorie(
Context context, View view, ActivityType activityType, double calorie) {
if (activityType == ActivityType.INVALID) {
view.setVisibility(View.GONE);
} else {
view.setVisibility(View.VISIBLE);
setItem(context, view, R.string.stats_calorie,
String.format(Locale.getDefault(), CALORIES_FORMAT, calorie),
context.getString(R.string.unit_calorie));
}
: Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
setItem(context, view, labelId, value, null);
}
/**
@@ -355,7 +334,8 @@ public class StatsUtils {
* @param value the value, can be null
* @param unit the unit. Null to hide the unit
*/
private static void setItem(Context context, View view, int labelId, String value, String unit) {
private static void setItem(
Context context, View view, int labelId, CharSequence value, CharSequence unit) {
TextView labelTextView = (TextView) view.findViewById(R.id.stats_label);
TextView valueTextView = (TextView) view.findViewById(R.id.stats_value);
TextView unitTextView = (TextView) view.findViewById(R.id.stats_unit);
@@ -81,7 +81,7 @@ public class StringUtils {
*/
public static String formatElapsedTime(long time) {
/*
* Temporary workaround for DateUtils.formatElapsedTime(time / 1000). In API
* Temporary workaround for DateUtils.formatElapsedTime(time * MS_TO_S). In API
* level 17, it returns strings like "1:0:00" instead of "1:00:00", which
* breaks several unit tests.
*/
@@ -91,7 +91,7 @@ public class StringUtils {
long hours = 0;
long minutes = 0;
long seconds = 0;
long elapsedSeconds = time / 1000;
long elapsedSeconds = (long) (time * UnitConversions.MS_TO_S);
if (elapsedSeconds >= 3600) {
hours = elapsedSeconds / 3600;
@@ -149,6 +149,19 @@ public class StringUtils {
}
}
public static String formatWeight(double value) {
return formatDecimal(value, 1);
}
public static String formatDecimal(double value) {
return formatDecimal(value, 2);
}
private static String formatDecimal(double value, int precision) {
String result = String.format(Locale.getDefault(), "%1$,." + precision + "f", value);
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
}
/**
* Gets the distance in an array of two strings. The first string is the
* distance. The second string is the unit. The first string is null if the
@@ -183,7 +196,7 @@ public class StringUtils {
unitId = R.string.unit_feet;
}
}
result[0] = String.format("%.2f", distance);
result[0] = formatDecimal(distance);
result[1] = context.getString(unitId);
return result;
}
@@ -217,13 +230,13 @@ public class StringUtils {
speed *= UnitConversions.KM_TO_MI;
}
if (reportSpeed) {
result[0] = String.format("%.2f", speed);
result[0] = StringUtils.formatDecimal(speed);
} else {
// convert from hours to minutes
double pace = speed == 0 ? 0.0 : 60.0 / speed;
int minutes = (int) pace;
int seconds = (int) Math.round((pace - minutes) * 60.0);
result[0] = String.format("%d:%02d", minutes, seconds);
result[0] = String.format(Locale.US, "%d:%02d", minutes, seconds);
}
return result;
}
@@ -301,7 +314,7 @@ public class StringUtils {
if (fractional != null) {
// Regex ensures fractional part is in (0,1)
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
long fractionalMillis = (long) (fractionalSeconds * UnitConversions.S_TO_MS);
time += fractionalMillis;
}
@@ -350,7 +363,7 @@ public class StringUtils {
}
int[] parts = new int[3];
long seconds = time / 1000;
long seconds = (long) (time * UnitConversions.MS_TO_S);
parts[0] = (int) (seconds % 60);
int minutes = (int) (seconds / 60);
parts[1] = minutes % 60;
@@ -21,8 +21,13 @@ import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.Pair;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -100,10 +105,10 @@ public class TrackIconUtils {
*/
public static int getIconDrawable(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.drawable.ic_track_walk;
return R.drawable.ic_track_generic;
}
Pair<Integer, Integer> pair = map.get(iconValue);
return pair == null ? R.drawable.ic_track_walk : pair.second;
return pair == null ? R.drawable.ic_track_generic : pair.second;
}
/**
@@ -137,6 +142,9 @@ public class TrackIconUtils {
* @param activityType the activity type
*/
public static String getIconValue(Context context, String activityType) {
if (activityType == null || activityType.equals("")) {
return "";
}
if (inList(context, activityType, airplane)) {
return AIRPLANE;
}
@@ -196,6 +204,35 @@ public class TrackIconUtils {
return output;
}
public static void setIconSpinner(Spinner spinner, String iconValue) {
@SuppressWarnings("unchecked")
ArrayAdapter<StringBuilder> adapter = (ArrayAdapter<StringBuilder>) spinner.getAdapter();
StringBuilder stringBuilder = adapter.getItem(0);
stringBuilder.delete(0, stringBuilder.length());
stringBuilder.append(iconValue);
adapter.notifyDataSetChanged();
}
public static ArrayAdapter<StringBuilder> getIconSpinnerAdapter(
final Context context, String iconValue) {
return new ArrayAdapter<StringBuilder>(context, android.R.layout.simple_spinner_item,
new StringBuilder[] { new StringBuilder(iconValue) }) {
@Override
public View getView(int position, View convertView, android.view.ViewGroup parent) {
ImageView imageView = convertView != null ? (ImageView) convertView
: new ImageView(getContext());
Bitmap source = BitmapFactory.decodeResource(
context.getResources(), TrackIconUtils.getIconDrawable(getItem(position).toString()));
if (ApiAdapterFactory.getApiAdapter().isSpinnerBackgroundLight()) {
source = TrackIconUtils.invertBitmap(source);
}
imageView.setImageBitmap(source);
imageView.setPadding(4, 4, -4, -4);
return imageView;
}
};
}
/**
* Returns true if the activity type is in the list.
*
@@ -0,0 +1,70 @@
/*
* Copyright 2013 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 com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.maps.mytracks.R;
import android.content.Context;
/**
* Utilities for updating track.
*
* @author Jimmy Shih
*/
public class TrackUtils {
private TrackUtils() {}
public static void updateTrack(Context context, Track track, String name, String category,
String description, MyTracksProviderUtils myTracksProviderUtils,
TrackRecordingServiceConnection trackRecordingServiceConnection, boolean newWeight) {
if (name != null) {
track.setName(name);
}
boolean updateCalorie = false;
if (category != null) {
updateCalorie = !category.equals(track.getCategory()) || newWeight;
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (description != null) {
track.setDescription(description);
}
track.setModifiedTime(System.currentTimeMillis());
myTracksProviderUtils.updateTrack(track);
if (updateCalorie) {
if (track.getId() == PreferencesUtils.getLong(context, R.string.recording_track_id_key)) {
// Update calorie through track recording service
TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
} else {
CalorieUtils.updateTrackCalorie(context, track);
}
}
boolean driveSync = PreferencesUtils.getBoolean(
context, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
if (driveSync) {
PreferencesUtils.addToList(context, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
}
}
}
@@ -24,11 +24,13 @@ public class UnitConversions {
private UnitConversions() {}
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// Time
// multiplication factor to convert seconds to milliseconds
public static final double S_TO_MS = 1000.0;
// multiplication factor to convert milliseconds to seconds
public static final double MS_TO_S = 1 / 1000.0;
public static final double MS_TO_S = 1 / S_TO_MS;
// multiplication factor to convert minutes to seconds
public static final double MIN_TO_S = 60.0;
@@ -42,15 +44,11 @@ public class UnitConversions {
// multiplication factor to convert minutes to hours
public static final double MIN_TO_HR = 1 / HR_TO_MIN;
// Distance
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// multiplication factor to convert kilograms to pounds
public static final double KG_TO_LB = 2.2046;
// multiplication factor to convert pounds to kilograms
public static final double LB_TO_KG = 1 / KG_TO_LB;
// multiplication factor to convert miles to kilometers
public static final double MI_TO_KM = 1 / KM_TO_MI;
@@ -60,13 +58,11 @@ public class UnitConversions {
// multiplication factor to convert feet to miles
public static final double FT_TO_MI = 1 / MI_TO_FT;
// multiplication factor to covert kilometers to meters
public static final double KM_TO_M = 1000.0;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / 1000.0;
public static final double KM_TO_M = 1 / M_TO_KM;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = 3.6;
public static final double M_TO_KM = 1 / KM_TO_M;
// multiplication factor to convert meters to miles
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
@@ -74,12 +70,33 @@ public class UnitConversions {
// multiplication factor to convert meters to feet
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
// Weight
// multiplication factor to convert kilograms to pounds
public static final double KG_TO_LB = 2.2046;
// multiplication factor to convert pounds to kilograms
public static final double LB_TO_KG = 1 / KG_TO_LB;
// Calories
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// multiplication factor to convert liter to kcal
public static final double L_TO_KCAL = 5.0;
// multiplication factor to convert watt to kgm/min
public static final double W_TO_KGM = 6.12;
// multiplication factor to convert kgm to kcal
public static final double KGM_TO_KCAL = 1 / 427.0;
// Others
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
// multiplication factor to convert degrees to radians
public static final double DEG_TO_RAD = Math.PI / 180.0;
// multiplication factor to convert Kcal to joules.
public static final double KCAL_TO_J = 4184;
// multiplication factor to convert joules to Kcal.
public static final double J_TO_KCAL = 1 / KCAL_TO_J;
}