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
@@ -19,7 +19,7 @@ package com.google.android.apps.mytracks;
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.PreferencesUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
@@ -38,7 +38,7 @@ public class AggregatedStatsActivity extends AbstractMyTracksActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatsUtils.setTripStatisticsValues(
this, this, null, getTripStatistics(), PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
this, this, null, getTripStatistics(), ActivityType.WALKING, null);
StatsUtils.setLocationValues(this, this, null, null, false);
}
@@ -30,12 +30,6 @@ public class Constants {
*/
public static final String ACCOUNT_TYPE = "com.google";
/**
* Maximum number of track points that will be loaded at one time. With
* recording frequency of 2 seconds, 20000 corresponds to 11.1 hours.
*/
public static final int MAX_LOADED_TRACK_POINTS = 20000;
/**
* Maximum number of waypoints that will be loaded at one time.
*/
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -32,6 +33,9 @@ import android.os.AsyncTask;
*/
public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private DeleteActivity deleteActivity;
private final long[] trackIds;
private final Context context;
@@ -82,7 +86,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
if (isCancelled()) {
return false;
}
Thread.sleep(1000);
Thread.sleep(ONE_SECOND);
}
} catch (InterruptedException e) {
return false;
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -41,7 +42,9 @@ import android.widget.TextView;
public class SensorStateActivity extends AbstractMyTracksActivity {
private static final String TAG = SensorStateActivity.class.getName();
private static final long ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Handler handler;
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -38,7 +39,9 @@ import android.widget.TextView;
public class TrackController {
private static final String TAG = TrackController.class.getSimpleName();
private static final int ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private final Activity activity;
private final TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -23,6 +23,8 @@ import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.fragments.ChartFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
import com.google.android.apps.mytracks.fragments.ConfirmDeleteDialogFragment;
import com.google.android.apps.mytracks.fragments.ExportDialogFragment;
import com.google.android.apps.mytracks.fragments.ExportDialogFragment.ExportCaller;
@@ -37,11 +39,15 @@ import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection
import com.google.android.apps.mytracks.settings.SettingsActivity;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleFeedbackUtils;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.TrackUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -74,7 +80,8 @@ import java.util.Date;
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class TrackDetailActivity extends AbstractSendToGoogleActivity implements ExportCaller {
public class TrackDetailActivity extends AbstractSendToGoogleActivity
implements ChooseActivityTypeCaller, ExportCaller {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
@@ -612,4 +619,23 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
}
setTitle(title);
}
public void chooseActivityType(String category) {
ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
}
@Override
public void onChooseActivityTypeDone(String iconValue, boolean newWeight) {
Track track = myTracksProviderUtils.getTrack(trackId);
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
TrackUtils.updateTrack(
this, track, null, category, null, myTracksProviderUtils, trackRecordingServiceConnection, newWeight);
// Add toast if cannot calculate calorie
if (CalorieUtils.getActivityType(this, category) == ActivityType.INVALID) {
String message = getString(R.string.stats_calorie_no_calculation, category);
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
}
@@ -21,17 +21,12 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackNameUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.TrackUtils;
import com.google.android.maps.mytracks.R;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
@@ -42,7 +37,6 @@ import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
/**
@@ -70,6 +64,8 @@ public class TrackEditActivity extends AbstractMyTracksActivity
private AutoCompleteTextView activityType;
private Spinner activityTypeIcon;
private EditText description;
private boolean newWeight = false;
@Override
protected void onCreate(Bundle bundle) {
@@ -101,14 +97,14 @@ public class TrackEditActivity extends AbstractMyTracksActivity
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
activityType.setAdapter(adapter);
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
setActivityTypeIcon(TrackIconUtils.getIconValue(
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position)));
}
});
activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
setActivityTypeIcon(TrackIconUtils.getIconValue(
@@ -125,55 +121,28 @@ public class TrackEditActivity extends AbstractMyTracksActivity
iconValue = track.getIcon();
}
// Add a border around the icon
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.ic_track_airplane, options);
final int padding = 4;
final int width = options.outWidth + 2 * padding;
final int height = options.outHeight + 2 * padding;
activityTypeIcon = (Spinner) findViewById(R.id.track_edit_activity_type_icon);
ArrayAdapter<StringBuilder> activityTypeIconAdapter = new ArrayAdapter<StringBuilder>(
this, 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(
getResources(), TrackIconUtils.getIconDrawable(getItem(position).toString()));
if (ApiAdapterFactory.getApiAdapter().isSpinnerBackgroundLight()) {
source = TrackIconUtils.invertBitmap(source);
}
imageView.setImageBitmap(source);
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
}
};
activityTypeIcon.setAdapter(activityTypeIconAdapter);
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
new ChooseActivityTypeDialogFragment().show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
}
return true;
}
});
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
@Override
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
new ChooseActivityTypeDialogFragment().show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
return true;
} else {
return false;
}
return true;
}
});
@@ -184,36 +153,9 @@ public class TrackEditActivity extends AbstractMyTracksActivity
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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());
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);
if (driveSync) {
PreferencesUtils.addToList(TrackEditActivity.this, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
}
TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(),
activityType.getText().toString(), description.getText().toString(),
myTracksProviderUtils, trackRecordingServiceConnection, newWeight);
finish();
}
});
@@ -261,26 +203,18 @@ public class TrackEditActivity extends AbstractMyTracksActivity
protected int getLayoutResId() {
return R.layout.track_edit;
}
/**
* Sets the activity type icon.
*
* @param value the icon value
*/
@SuppressWarnings("unchecked")
private void setActivityTypeIcon(String value) {
iconValue = value;
ArrayAdapter<StringBuilder> adapter =
(ArrayAdapter<StringBuilder>) activityTypeIcon.getAdapter();
StringBuilder stringBuilder = adapter.getItem(0);
stringBuilder.delete(0, stringBuilder.length());
stringBuilder.append(value);
adapter.notifyDataSetChanged();
TrackIconUtils.setIconSpinner(activityTypeIcon, value);
}
@Override
public void onChooseActivityTypeDone(String value) {
activityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
public void onChooseActivityTypeDone(String value, boolean hasNewWeight) {
if (!newWeight) {
newWeight = hasNewWeight;
}
setActivityTypeIcon(value);
activityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
}
}
@@ -91,50 +91,58 @@ public class MyTracksProvider extends ContentProvider {
} else {
// Incremental upgrades. One if statement per DB version.
// Add track points SENSOR column
// Add track point SENSOR column
if (oldVersion <= 17) {
Log.w(TAG, "Upgrade DB: Adding sensor column.");
Log.w(TAG, "Upgrade DB: Adding track point sensor column.");
db.execSQL("ALTER TABLE " + TrackPointsColumns.TABLE_NAME + " ADD "
+ TrackPointsColumns.SENSOR + " BLOB");
}
// Add tracks TABLEID column
// Add track TABLEID column
if (oldVersion <= 18) {
Log.w(TAG, "Upgrade DB: Adding tableid column.");
Log.w(TAG, "Upgrade DB: Adding track tableid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.TABLEID
+ " STRING");
}
// Add tracks ICON column
// Add track ICON column
if (oldVersion <= 19) {
Log.w(TAG, "Upgrade DB: Adding icon column.");
Log.w(TAG, "Upgrade DB: Adding track icon column.");
db.execSQL(
"ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.ICON + " STRING");
}
// Add track DRIVEID, MODIFIEDTIME, and SHAREDWITHME columns
if (oldVersion <= 20) {
Log.w(TAG, "Upgrade DB: Adding driveid column.");
Log.w(TAG, "Upgrade DB: Adding track driveid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.DRIVEID
+ " STRING");
Log.w(TAG, "Upgrade DB: Adding modifiedtime column.");
Log.w(TAG, "Upgrade DB: Adding track modifiedtime column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD "
+ TracksColumns.MODIFIEDTIME + " INTEGER");
Log.w(TAG, "Upgrade DB: Adding sharedwithme column.");
Log.w(TAG, "Upgrade DB: Adding track sharedwithme column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD "
+ TracksColumns.SHAREDWITHME + " INTEGER");
Log.w(TAG, "Upgrade DB: Adding sharedowner column.");
Log.w(TAG, "Upgrade DB: Adding track sharedowner column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.SHAREDOWNER
+ " STRING");
}
// Add waypoint CALORIE and PHOTOURL columns. Add track CALORIE column.
if (oldVersion <= 21) {
Log.w(TAG, "Upgrade DB: Adding photo url column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD " + WaypointsColumns.PHOTOURL
+ " STRING");
Log.w(TAG, "Upgrade DB: Adding waypoint calorie column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD "
+ WaypointsColumns.CALORIE + " FLOAT");
Log.w(TAG, "Upgrade DB: Adding waypoint photo url column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD "
+ WaypointsColumns.PHOTOURL + " STRING");
Log.w(TAG, "Upgrade DB: Adding track calorie column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.CALORIE
+ " INTEGER");
+ " FLOAT");
}
}
}
@@ -360,7 +360,8 @@ public class SearchEngine {
}
// Score recent tracks higher.
long timeAgoHours = (query.currentTimestamp - timestamp) / (60L * 60L * 1000L);
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
if (timeAgoHours > 0L) {
return squash(timeAgoHours);
} else {
@@ -492,52 +492,60 @@ public class TrackDataHub implements DataSourceListener {
long lastTrackPointId = myTracksProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(selectedTrackId,
localLastSeenLocationId + 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
boolean includeNextPoint = false;
while (iterator.hasNext()) {
Location location = iterator.next();
long locationId = iterator.getLocationId();
LocationIterator locationIterator = null;
// Stop if past the last wanted point
if (maxPointId != -1L && locationId > maxPointId) {
break;
}
try {
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(selectedTrackId,
localLastSeenLocationId + 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
if (localFirstSeenLocationId == -1) {
localFirstSeenLocationId = locationId;
}
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
long locationId = locationIterator.getLocationId();
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) {
// TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSegmentSplit(location);
includeNextPoint = true;
// Stop if past the last wanted point
if (maxPointId != -1L && locationId > maxPointId) {
break;
}
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
if (localFirstSeenLocationId == -1) {
localFirstSeenLocationId = locationId;
}
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) {
// TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
trackDataListener.onSegmentSplit(location);
includeNextPoint = true;
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
}
}
}
}
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
}
iterator.close();
if (updateSamplingState) {
numLoadedPoints = localNumLoadedPoints;
@@ -453,7 +453,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
if (tripStatisticsUpdater != null) {
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
@@ -16,12 +16,16 @@
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
@@ -30,20 +34,22 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* A DialogFragment to choose an activity type.
*
*
* @author apoorvn
*/
public class ChooseActivityTypeDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
*
* @author apoorvn
*/
public interface ChooseActivityTypeCaller {
@@ -51,12 +57,28 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
/**
* Called when choose activity type is done.
*/
public void onChooseActivityTypeDone(String iconValue);
public void onChooseActivityTypeDone(String iconValue, boolean newWeight);
}
public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType";
private static final String KEY_CATEGORY = "category";
private ChooseActivityTypeCaller caller;
private ChooseActivityTypeImageAdapter imageAdapter;
private AlertDialog alertDialog;
private View weightContainer;
private TextView weight;
private String weightValue;
public static ChooseActivityTypeDialogFragment newInstance(String category) {
Bundle bundle = new Bundle();
bundle.putString(KEY_CATEGORY, category);
ChooseActivityTypeDialogFragment fragment = new ChooseActivityTypeDialogFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onAttach(Activity activity) {
@@ -71,12 +93,31 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
GridView gridView =
(GridView) getActivity().getLayoutInflater().inflate(R.layout.choose_activity_type, null);
final List<String> iconValues = TrackIconUtils.getAllIconValues();
View view = getActivity().getLayoutInflater().inflate(R.layout.choose_activity_type, null);
GridView gridView = (GridView) view.findViewById(R.id.choose_activity_type_grid_view);
weightContainer = view.findViewById(R.id.choose_activity_type_weight_container);
TextView weightLabel = (TextView) view.findViewById(R.id.choose_activity_type_weight_label);
weightLabel.setText(
PreferencesUtils.isMetricUnits(getActivity()) ? R.string.description_weight_metric
: R.string.description_weight_imperial);
weight = (TextView) view.findViewById(R.id.choose_activity_type_weight);
ImageButton imageButton = (ImageButton) view.findViewById(
R.id.choose_activity_type_weight_image_button);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
double weightDefault = PreferencesUtils.WEIGHT_DEFAULT;
if (!PreferencesUtils.isMetricUnits(getActivity())) {
weightDefault *= UnitConversions.KG_TO_LB;
}
weight.setText(StringUtils.formatWeight(weightDefault));
}
});
List<Integer> imageIds = new ArrayList<Integer>();
for (String iconValue : iconValues) {
for (String iconValue : TrackIconUtils.getAllIconValues()) {
imageIds.add(TrackIconUtils.getIconDrawable(iconValue));
}
@@ -88,18 +129,77 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
int height = options.outHeight + 2 * padding;
gridView.setColumnWidth(width);
ChooseActivityTypeImageAdapter imageAdapter =
new ChooseActivityTypeImageAdapter(getActivity(), imageIds, width, height, padding);
imageAdapter = new ChooseActivityTypeImageAdapter(
getActivity(), imageIds, width, height, padding);
gridView.setAdapter(imageAdapter);
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
dismiss();
caller.onChooseActivityTypeDone(iconValues.get(position));
alertDialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(true);
imageAdapter.setSelected(position);
imageAdapter.notifyDataSetChanged();
updateWeightContainer(position);
}
});
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setTitle(R.string.track_edit_activity_type_hint).setView(gridView).create();
alertDialog = new AlertDialog.Builder(getActivity()).setNegativeButton(
R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean newWeight = false;
if (weightContainer.getVisibility() == View.VISIBLE) {
String newValue = weight.getText().toString();
if (!newValue.equals(weightValue)) {
newWeight = true;
PreferencesUtils.storeWeightValue(getActivity(), newValue);
}
}
int selected = imageAdapter.getSelected();
caller.onChooseActivityTypeDone(
TrackIconUtils.getAllIconValues().get(selected), newWeight);
}
}).setTitle(R.string.track_edit_activity_type_hint).setView(view).create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
int position = getPosition();
alertDialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(position != -1);
if (position != -1) {
imageAdapter.setSelected(position);
imageAdapter.notifyDataSetChanged();
}
updateWeightContainer(position);
double weightDisplayValue = PreferencesUtils.getWeightDisplayValue(getActivity());
weightValue = StringUtils.formatWeight(weightDisplayValue);
weight.setText(weightValue);
}
});
return alertDialog;
}
private int getPosition() {
String category = getArguments().getString(KEY_CATEGORY);
if (category == null) {
return -1;
}
String iconValue = TrackIconUtils.getIconValue(getActivity(), category);
if (iconValue.equals("")) {
return -1;
}
List<String> iconValues = TrackIconUtils.getAllIconValues();
for (int i = 0; i < iconValues.size(); i++) {
if (iconValues.get(i).equals(iconValue)) {
return i;
}
}
return -1;
}
private void updateWeightContainer(int position) {
boolean showWeight = position == 0 || position == 1 || position == 2;
weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE);
}
}
@@ -16,7 +16,10 @@
package com.google.android.apps.mytracks.fragments;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
@@ -36,6 +39,7 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final int width;
private final int height;
private final int padding;
private int selected = -1;
public ChooseActivityTypeImageAdapter(
Context context, List<Integer> imageIds, int width, int height, int padding) {
@@ -61,6 +65,14 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
return 0;
}
public int getSelected() {
return selected;
}
public void setSelected(int position) {
selected = position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
@@ -69,6 +81,11 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
} else {
imageView = (ImageView) convertView;
}
if (position == selected) {
imageView.setBackgroundResource(R.drawable.list_selector_background_transition_holo_dark);
} else {
imageView.setBackgroundColor(Color.TRANSPARENT);
}
imageView.setImageResource(imageIds.get(position));
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
@@ -22,9 +22,10 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
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.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PhotoUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -128,6 +129,9 @@ public class MarkerDetailFragment extends Fragment {
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
textGradient.setVisibility(visibility);
waypointInfo.setVisibility(visibility);
if (visibility == View.VISIBLE) {
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
}
}
});
textGradient = (ImageView) view.findViewById(R.id.marker_detail_waypoint_text_gradient);
@@ -277,8 +281,12 @@ public class MarkerDetailFragment extends Fragment {
TextView name = (TextView) getView().findViewById(R.id.marker_detail_statistics_name);
setTextView(name, waypoint.getName());
StatsUtils.setTripStatisticsValues(getActivity(), null, getView(),
waypoint.getTripStatistics(), PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
Track track = myTracksProviderUtils.getTrack(waypoint.getTrackId());
ActivityType activityType = track != null ? CalorieUtils.getActivityType(
getActivity(), track.getCategory())
: ActivityType.INVALID;
StatsUtils.setTripStatisticsValues(
getActivity(), null, getView(), waypoint.getTripStatistics(), activityType, null);
StatsUtils.setLocationValues(getActivity(), null, getView(), waypoint.getLocation(), false);
}
}
@@ -23,9 +23,13 @@ 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.stats.TripStatistics;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
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.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.location.Location;
@@ -33,9 +37,12 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Spinner;
import java.util.EnumSet;
@@ -49,13 +56,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public static final String STATS_FRAGMENT_TAG = "statsFragment";
private static final int ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private TrackDataHub trackDataHub;
private Handler handler;
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private String category = "";
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
// A runnable to update the total time field.
@@ -81,13 +90,34 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
handler = new Handler();
updateUi(getActivity());
Spinner activityTypeIcon = (Spinner) getView().findViewById(R.id.stats_activity_type_icon);
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
updateUi(getActivity());
if (isSelectedTrackRecording()) {
handler.post(updateTotalTime);
}
@@ -106,8 +136,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
if (isResumed()) {
lastTripStatistics = track != null ? track.getTripStatistics() : null;
category = track != null ? track.getCategory() : "";
updateUi(getActivity());
}
}
@@ -260,9 +291,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
* Updates the UI.
*/
private void updateUi(FragmentActivity activity) {
StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics,
trackDataHub == null ? PreferencesUtils.RECORDING_TRACK_ID_DEFAULT
: trackDataHub.getSelectedTrackId());
ActivityType activityType = CalorieUtils.getActivityType(activity, category);
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
StatsUtils.setTripStatisticsValues(
activity, activity, null, lastTripStatistics, activityType, trackIconValue);
StatsUtils.setLocationValues(
activity, activity, null, lastLocation, isSelectedTrackRecording());
}
@@ -59,6 +59,7 @@ public class PreferenceBackupHelper {
doNotBackup.add(context.getString(R.string.google_account_key));
doNotBackup.add(context.getString(R.string.recording_track_id_key));
doNotBackup.add(context.getString(R.string.recording_track_paused_key));
doNotBackup.add(context.getString(R.string.weight_key));
}
/**
@@ -134,16 +134,18 @@ public class FileTrackExporter implements TrackExporter {
boolean wroteSegment = false;
boolean isLastLocationValid = false;
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
int locationNumber = 0;
LocationIterator locationIterator = null;
try {
int locationNumber = 0;
while (iterator.hasNext()) {
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
while (locationIterator.hasNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Location location = iterator.next();
Location location = locationIterator.next();
setLocationTime(location, offset);
locationNumber++;
@@ -195,7 +197,9 @@ public class FileTrackExporter implements TrackExporter {
trackWriter.writeEndTrack(track, null);
}
} finally {
iterator.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -172,7 +173,8 @@ public class TcxTrackWriter implements TrackWriter {
if (printWriter != null) {
String startTime = StringUtils.formatDateTimeIso8601(
track.getTripStatistics().getStartTime());
long totalTimeInSeconds = track.getTripStatistics().getTotalTime() / 1000;
long totalTimeInSeconds = (long) (track.getTripStatistics().getTotalTime()
* UnitConversions.MS_TO_S);
printWriter.println("<Activities>");
printWriter.println("<Activity Sport=\"" + sportType.getName() + "\">");
@@ -18,27 +18,30 @@ package com.google.android.apps.mytracks.io.file.importer;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
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.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -105,6 +108,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private final long importTrackId;
private final MyTracksProviderUtils myTracksProviderUtils;
private final int recordingDistanceInterval;
private final double weight;
private final List<Long> trackIds;
private final List<Waypoint> waypoints;
@@ -142,6 +146,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
this.weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
trackIds = new ArrayList<Long>();
waypoints = new ArrayList<Waypoint>();
}
@@ -209,20 +215,20 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (track == null) {
return;
}
Cursor trackPointCursor = null;
int waypointPosition = -1;
Waypoint waypoint = null;
Location location = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
LocationIterator locationIterator = null;
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
try {
trackPointCursor = myTracksProviderUtils.getTrackPointCursor(track.getId(), -1L, -1, false);
if (trackPointCursor == null) {
return;
}
int waypointPosition = -1;
Waypoint waypoint = null;
int trackPointPosition = -1;
Location trackPoint = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
while (true) {
if (waypoint == null) {
@@ -233,28 +239,25 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
return;
}
}
if (trackPoint == null) {
trackPointPosition++;
trackPoint = trackPointCursor.moveToPosition(trackPointPosition) ? myTracksProviderUtils
.createTrackPoint(trackPointCursor)
: null;
if (trackPoint == null) {
if (location == null) {
if (!locationIterator.hasNext()) {
// No more track points. Ignore the rest of the waypoints.
return;
}
trackTripStatisticstrackUpdater.addLocation(trackPoint, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
markerTripStatisticsUpdater.addLocation(trackPoint, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
location = locationIterator.next();
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
}
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
trackPoint = null;
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
if (waypoint.getLocation().getTime() > location.getTime()) {
location = null;
} else if (waypoint.getLocation().getTime() < location.getTime()) {
waypoint = null;
} else {
// The waypoint location time matches the track point time
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude()
&& trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
if (location.getLatitude() == waypoint.getLocation().getLatitude()
&& location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Get tripStatistics, description, and icon
TripStatistics tripStatistics;
@@ -262,7 +265,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
String icon;
if (waypoint.getType() == WaypointType.STATISTICS) {
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
markerTripStatisticsUpdater = new TripStatisticsUpdater(trackPoint.getTime());
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypointDescription = new DescriptionGeneratorImpl(context)
.generateWaypointDescription(tripStatistics);
icon = context.getString(R.string.marker_statistics_icon_url);
@@ -279,15 +282,15 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
-1L, -1L, trackPoint, tripStatistics, waypoint.getPhotoUrl());
-1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
myTracksProviderUtils.insertWaypoint(newWaypoint);
}
waypoint = null;
}
}
} finally {
if (trackPointCursor != null) {
trackPointCursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -412,10 +415,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* last two points. GPS points tend to have some inherent imprecision,
* speed and bearing will likely be off, so the statistics for things
* like max speed will also be off.
*/
float speed = trackData.lastLocationInCurrentSegment.distanceTo(location) * 1000.0f
/ timeDifference;
location.setSpeed(speed);
*/
double duration = timeDifference * UnitConversions.MS_TO_S;
double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration;
location.setSpeed((float) speed);
}
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
}
@@ -469,8 +472,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (importTrackId == -1L) {
return null;
}
return FileUtils.getPath(
String path = FileUtils.getPath(
FileUtils.PICTURES_DIR, Long.toString(importTrackId), fileName);
return Uri.fromFile(new File(path)).toString();
}
/**
@@ -486,8 +490,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
latitudeValue = Double.parseDouble(latitude);
longitudeValue = Double.parseDouble(longitude);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage(
String.format("Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
throw new SAXException(createErrorMessage(String.format(
Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
}
Double altitudeValue = null;
if (altitude != null) {
@@ -495,7 +499,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
altitudeValue = Double.parseDouble(altitude);
} catch (NumberFormatException e) {
throw new SAXException(
createErrorMessage(String.format("Unable to parse altitude: %s", altitude)), e);
createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)),
e);
}
}
@@ -507,7 +512,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
timeValue = StringUtils.getTime(time);
} catch (IllegalArgumentException e) {
throw new SAXException(
createErrorMessage(String.format("Unable to parse time: %s", time)), e);
createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
}
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
@@ -548,8 +553,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
location.getTime() != -1L ? location.getTime() : trackData.importTime);
}
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
ActivityType activityType = CalorieUtils.getActivityType(context, category);
trackData.tripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
trackData.numBufferedLocations++;
@@ -156,6 +156,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
name = null;
description = null;
category = null;
photoUrl = null;
latitude = attributes.getValue(ATTRIBUTE_LAT);
longitude = attributes.getValue(ATTRIBUTE_LON);
altitude = null;
@@ -91,6 +91,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
public void startElement(String uri, String localName, String tag, Attributes attributes)
throws SAXException {
if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
/*
* Note that a track is contained in a Placemark, calling onWaypointStart
* will clear various track variables like name, category, and
* description.
*/
onWaypointStart();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
trackStarted = true;
@@ -110,6 +115,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (tag.equals(TAG_KML)) {
onFileEnd();
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
/*
* Note that a track is contained in a Placemark, calling onWaypointend is
* save since waypointType is not set for a track.
*/
onWaypointEnd();
} else if (localName.equals(TAG_COORDINATES)) {
onWaypointLocationEnd();
@@ -159,12 +168,12 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
name = null;
description = null;
category = null;
photoUrl = null;
latitude = null;
longitude = null;
altitude = null;
time = null;
waypointType = null;
photoUrl = null;
}
/**
@@ -6,6 +6,7 @@ import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
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;
@@ -227,23 +228,19 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
*/
private boolean uploadAllTrackPoints(Fusiontables fusiontables, String tableId, Track track)
throws IOException {
Cursor cursor = null;
int numberOfPoints = track.getNumberOfPoints();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
int readCount = 0;
LocationIterator locationIterator = null;
try {
cursor = myTracksProviderUtils.getTrackPointCursor(trackId, -1L, -1, false);
if (cursor == null) {
Log.d(TAG, "Location cursor is null");
return false;
}
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
trackId, -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
int count = cursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
Location location = myTracksProviderUtils.createTrackPoint(cursor);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
locations.add(location);
if (LocationUtils.isValidLocation(location)) {
@@ -258,13 +255,13 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
}
// Upload periodically
int readCount = i + 1;
readCount++;
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
if (!prepareAndUploadPoints(fusiontables, tableId, track, locations, false)) {
Log.d(TAG, "Unable to upload points");
return false;
}
updateProgress(readCount, count);
updateProgress(readCount, numberOfPoints);
locations.clear();
}
}
@@ -285,8 +282,8 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
}
return true;
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -19,6 +19,7 @@ import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
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.io.gdata.GDataClientFactory;
@@ -235,33 +236,29 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
*/
@VisibleForTesting
boolean uploadAllTrackPoints(Track track) {
Cursor cursor = null;
int numberOfPoints = track.getNumberOfPoints();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
// For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (numberOfPoints / 250.0));
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
int readCount = 0;
LocationIterator locationIterator = null;
try {
cursor = myTracksProviderUtils.getTrackPointCursor(trackId, -1L, -1, false);
if (cursor == null) {
Log.d(TAG, "Location cursor is null");
return false;
}
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
trackId, -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
int count = cursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
// For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (count / 250.0));
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
Location location = myTracksProviderUtils.createTrackPoint(cursor);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
locations.add(location);
if (LocationUtils.isValidLocation(location)) {
@@ -279,20 +276,20 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
}
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (i % elevationSamplingFrequency == 0) {
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
if (readCount % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsUpdater.getTripStatistics().getTotalDistance());
elevations.add(tripStatisticsUpdater.getSmoothedElevation());
}
// Upload periodically
int readCount = i + 1;
readCount++;
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
if (!prepareAndUploadPoints(track, locations, false)) {
Log.d(TAG, "Unable to upload points");
return false;
}
updateProgress(readCount, count);
updateProgress(readCount, numberOfPoints);
locations.clear();
}
}
@@ -318,8 +315,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
}
return true;
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
@@ -44,6 +45,7 @@ import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackNameUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
@@ -59,7 +61,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.location.Location;
import android.location.LocationManager;
@@ -101,9 +102,14 @@ public class TrackRecordingService extends Service {
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
private static final String TAG = TrackRecordingService.class.getSimpleName();
private static final long ONE_SECOND = 1000; // in milliseconds
private static final long ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
// 1 minute in milliseconds
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
@VisibleForTesting
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
@@ -126,7 +132,8 @@ public class TrackRecordingService extends Service {
private int recordingGpsAccuracy;
private int autoResumeTrackTimeout;
private long currentRecordingInterval;
private double weight;
// The following variables are set when recording:
private TripStatisticsUpdater trackTripStatisticsUpdater;
private TripStatisticsUpdater markerTripStatisticsUpdater;
@@ -224,6 +231,10 @@ public class TrackRecordingService extends Service {
R.string.auto_resume_track_timeout_key,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.weight_key))) {
weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
}
}
};
@@ -650,31 +661,27 @@ public class TrackRecordingService extends Service {
}
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
Cursor cursor = null;
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
LocationIterator locationIterator = null;
try {
// TODO: how to handle very long track.
cursor = myTracksProviderUtils.getTrackPointCursor(
recordingTrackId, -1L, Constants.MAX_LOADED_TRACK_POINTS, true);
if (cursor == null) {
Log.e(TAG, "Cursor is null.");
} else {
if (cursor.moveToLast()) {
do {
Location location = myTracksProviderUtils.createTrackPoint(cursor);
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (location.getTime() > markerStartTime) {
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
}
} while (cursor.moveToPrevious());
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
if (location.getTime() > markerStartTime) {
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
}
}
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
startRecording(true);
@@ -1010,11 +1017,11 @@ public class TrackRecordingService extends Service {
try {
Uri uri = myTracksProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, true,
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);
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
} catch (SQLiteException e) {
/*
@@ -1377,23 +1384,14 @@ public class TrackRecordingService extends Service {
return;
}
double calorie = CalorieUtils.getTrackCalorie(context, track, -1L);
// Update to database
track.getTripStatistics().setCalorie(calorie);
myTracksProviderUtils.updateTrack(track);
double[] calories = CalorieUtils.updateTrackCalorie(context, track);
// Update track statistics
trackTripStatisticsUpdater.updateCalorie(calorie);
trackTripStatisticsUpdater.updateCalorie(calories[0]);
// 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);
// Update marker statistics
markerTripStatisticsUpdater.updateCalorie(calories[1]);
}
});
}
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Sensor.SensorState;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.bluetooth.BluetoothAdapter;
@@ -39,6 +40,8 @@ import java.util.ArrayList;
*/
public class BluetoothSensorManager extends SensorManager {
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
@@ -69,7 +72,7 @@ public class BluetoothSensorManager extends SensorManager {
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(1000L);
mutex.wait(ONE_SECOND);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
@@ -38,6 +38,7 @@ import android.util.Log;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.util.Locale;
/**
* Ant Sensor Manager.
@@ -396,7 +397,8 @@ public class AntSensorManager extends SensorManager {
if (channelConfig[channel].isInitializing()) {
if (message[AntMesg.MESG_DATA_OFFSET + 2] != 0) {
// Error response
Log.e(TAG, String.format("Error code(%#02x) on message ID(%#02x) on channel %d",
Log.e(TAG, String.format(Locale.US,
"Error code(%#02x) on message ID(%#02x) on channel %d",
message[AntMesg.MESG_DATA_OFFSET + 2], message[AntMesg.MESG_DATA_OFFSET + 1],
channel));
} else {
@@ -50,8 +50,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
static final float TTS_SPEECH_RATE = 0.9f;
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
private static final long HOUR_TO_MILLISECOND = 60 * 60 * 1000;
@VisibleForTesting
static final HashMap<String, String> SPEECH_PARAMS = new HashMap<String, String>();
static {
@@ -249,7 +248,9 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
} else {
speed = speed == 0 ? 0.0 : 1 / speed;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
rate = context.getString(paceId, getAnnounceTime((long) (speed * HOUR_TO_MILLISECOND)));
long time = (long) (speed * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(time));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers
@@ -17,22 +17,12 @@
package com.google.android.apps.mytracks.settings;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.text.InputType;
import android.util.Log;
import android.widget.EditText;
/**
* An activity for accessing stats settings.
@@ -41,25 +31,12 @@ import android.widget.EditText;
*/
public class StatsSettingsActivity extends AbstractSettingsActivity {
private static final String TAG = MapSettingsActivity.class.getSimpleName();
private CheckBoxPreference caloriePreference;
private EditTextPreference weightPreference;
private static final int WEIGHT_INPUT_DIALOG = 1;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.stats_settings);
caloriePreference = (CheckBoxPreference) findPreference(getString(R.string.stats_show_calorie_key));
weightPreference = (EditTextPreference) findPreference(getString(R.string.stats_weight_key));
configCaloriePreference();
configWeightPreference();
updateWeightSummary();
/*
* Note configureUnitsListPreference will trigger
* configureRateListPreference
@@ -72,27 +49,19 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
*/
private void configUnitsListPreference() {
@SuppressWarnings("deprecation")
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_units_key));
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_units_key));
OnPreferenceChangeListener listener = new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
configRateListPreference(PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue));
/*
* It is necessary to change the weight summary as they are in the same
* activity.
*/
updateWeightSummary(PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue));
// Recreate weight value dialog.
removeDialog(WEIGHT_INPUT_DIALOG);
onCreateDialog(WEIGHT_INPUT_DIALOG);
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
boolean metricUnits = PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue);
configRateListPreference(metricUnits);
return true;
}
};
String value = PreferencesUtils.getString(this, R.string.stats_units_key,
PreferencesUtils.STATS_UNITS_DEFAULT);
String value = PreferencesUtils.getString(
this, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_units_values);
String[] options = getResources().getStringArray(R.array.stats_units_options);
configureListPreference(listPreference, options, options, values, value, listener);
@@ -105,139 +74,13 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
*/
private void configRateListPreference(boolean metricUnits) {
@SuppressWarnings("deprecation")
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_rate_key));
String value = PreferencesUtils.getString(this, R.string.stats_rate_key,
PreferencesUtils.STATS_RATE_DEFAULT);
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_rate_key));
String value = PreferencesUtils.getString(
this, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_rate_values);
String[] options = getResources().getStringArray(
metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
configureListPreference(listPreference, options, options, values, value, null);
}
/**
* Configures the calorie preference.
*/
private void configCaloriePreference() {
caloriePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean value = (Boolean) newValue;
if (value) {
showDialog(WEIGHT_INPUT_DIALOG);
}
return true;
}
});
}
@Override
@Deprecated
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch (id) {
case WEIGHT_INPUT_DIALOG:
final EditText weightInput = new EditText(this);
weightInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
weightInput.setText(Integer.toString(getWeightDisplayValue(PreferencesUtils
.isMetricUnits(getApplicationContext()))));
weightInput.setSelectAllOnFocus(true);
dialog = (new AlertDialog.Builder(this))
.setMessage(R.string.settings_stats_calorie_weight_description)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int number) {
storeWeightValue(weightInput.getText().toString());
updateWeightSummary();
dialogInterface.cancel();
}
}).setTitle(R.string.settings_stats_calorie_weight).setView(weightInput).create();
break;
default:
break;
}
return dialog;
}
/**
* Configures the weight preference.
*/
private void configWeightPreference() {
weightPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
storeWeightValue((String) newValue);
updateWeightSummary();
return true;
}
});
weightPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference pref) {
int value = getWeightDisplayValue(PreferencesUtils.isMetricUnits(getApplicationContext()));
((EditTextPreference) pref).getEditText().setText(String.valueOf(value));
return true;
}
});
}
/**
* Updates the weight summary.
*/
private void updateWeightSummary() {
boolean metricUnits = PreferencesUtils.isMetricUnits(this);
updateWeightSummary(metricUnits);
}
/**
* Updates the weight summary.
*
* @param metricUnits true if metric units
*/
private void updateWeightSummary(boolean metricUnits) {
int displayValue = getWeightDisplayValue(metricUnits);
weightPreference.setSummary(getString(metricUnits ? R.string.value_integer_kilogram
: R.string.value_integer_pound, displayValue));
}
/**
* Gets the weight display value in metric or imperial depending on the
* preferred units.
*
* @param metricUnits true if metric units
*/
private int getWeightDisplayValue(boolean metricUnits) {
int value = PreferencesUtils.getInt(this, R.string.stats_weight_key,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (!metricUnits) {
value = (int) Math.round(value * UnitConversions.KG_TO_LB);
}
return value;
}
/**
* Stores the weight value, always in metric units.
*
* @param displayValue the display value
*/
private void storeWeightValue(String displayValue) {
/*
* TODO add a method to an abstract class or an utility class to avoid
* duplicating store preference logic in MapSettingsActivity.java.
*/
int value;
try {
value = Integer.parseInt(displayValue);
if (!PreferencesUtils.isMetricUnits(this)) {
value = (int) (value * UnitConversions.LB_TO_KG);
}
} catch (NumberFormatException e) {
Log.e(TAG, "invalid value " + displayValue);
value = PreferencesUtils.STATS_WEIGHT_DEFAULT;
}
PreferencesUtils.setInt(this, R.string.stats_weight_key, value);
}
}
@@ -131,7 +131,7 @@ public class TripStatisticsUpdater {
* calorie
*/
public void addLocation(Location location, int minRecordingDistance,
boolean calculateCalorie, ActivityType activityType, int weight) {
boolean calculateCalorie, ActivityType activityType, double weight) {
// Always update time
updateTime(location.getTime());
if (!LocationUtils.isValidLocation(location)) {
@@ -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;
}