Merge heads

This commit is contained in:
youtaol
2012-08-24 12:42:57 +08:00
parent bc37316fa5
commit 9859e4ff14
14 changed files with 373 additions and 345 deletions
+7 -2
View File
@@ -683,12 +683,17 @@ limitations under the License.
translation_description="On application start, message when there is no GPS on the device."> translation_description="On application start, message when there is no GPS on the device.">
No GPS is found on the device. No GPS is found on the device.
</string> </string>
<string name="gps_wait_for_signal" <string name="gps_wait_for_better_signal"
translation_description="In the main view, the message on the top indicating My Tracks is translation_description="In the main view, the message on the top indicating My Tracks is
waiting for a better GPS signal."> waiting for a better GPS signal.">
Waiting for a better GPS signal. Make sure the sky is visible. Waiting for a better GPS signal. Make sure the sky is visible.
</string> </string>
<string name="gps_wait_for_signal"
translation_description="In the main view, the message on the top indicating My Tracks is
waiting for a better GPS signal.">
Waiting for GPS signal. Make sure the sky is visible.
</string>
<!-- Help page --> <!-- Help page -->
<string name="help_about" <string name="help_about"
translation_description="In the 'Help' page, the button on the bottom to open the 'About My translation_description="In the 'Help' page, the button on the bottom to open the 'About My
@@ -75,6 +75,7 @@ public class TrackDataHub implements DataSourceListener {
private boolean metricUnits; private boolean metricUnits;
private boolean reportSpeed; private boolean reportSpeed;
private int minRequiredAccuracy; private int minRequiredAccuracy;
private int minRecordingDistance;
// Heading values // Heading values
private float lastHeading = 0; private float lastHeading = 0;
@@ -347,6 +348,20 @@ public class TrackDataHub implements DataSourceListener {
minRequiredAccuracy = PreferencesUtils.getInt(context, R.string.min_required_accuracy_key, minRequiredAccuracy = PreferencesUtils.getInt(context, R.string.min_required_accuracy_key,
PreferencesUtils.MIN_REQUIRED_ACCURACY_DEFAULT); PreferencesUtils.MIN_REQUIRED_ACCURACY_DEFAULT);
} }
if (key == null
|| key.equals(PreferencesUtils.getKey(context, R.string.min_recording_distance_key))) {
minRecordingDistance = PreferencesUtils.getInt(
context, R.string.min_recording_distance_key,
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
if (key != null) {
for (TrackDataListener trackDataListener :
trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
if (trackDataListener.onMinRecordingDistanceChanged(minRecordingDistance)) {
loadDataForListener(trackDataListener);
}
}
}
}
} }
}); });
} }
@@ -422,6 +437,7 @@ public class TrackDataHub implements DataSourceListener {
trackDataManager.getListeners(TrackDataType.PREFERENCE)) { trackDataManager.getListeners(TrackDataType.PREFERENCE)) {
trackDataListener.onMetricUnitsChanged(metricUnits); trackDataListener.onMetricUnitsChanged(metricUnits);
trackDataListener.onReportSpeedChanged(reportSpeed); trackDataListener.onReportSpeedChanged(reportSpeed);
trackDataListener.onMinRecordingDistanceChanged(minRecordingDistance);
} }
notifySelectedTrackChanged(trackDataManager.getListeners(TrackDataType.SELECTED_TRACK)); notifySelectedTrackChanged(trackDataManager.getListeners(TrackDataType.SELECTED_TRACK));
@@ -457,6 +473,7 @@ public class TrackDataHub implements DataSourceListener {
if (trackDataTypes.contains(TrackDataType.PREFERENCE)) { if (trackDataTypes.contains(TrackDataType.PREFERENCE)) {
trackDataListener.onMetricUnitsChanged(metricUnits); trackDataListener.onMetricUnitsChanged(metricUnits);
trackDataListener.onReportSpeedChanged(reportSpeed); trackDataListener.onReportSpeedChanged(reportSpeed);
trackDataListener.onMinRecordingDistanceChanged(minRecordingDistance);
} }
if (trackDataTypes.contains(TrackDataType.SELECTED_TRACK)) { if (trackDataTypes.contains(TrackDataType.SELECTED_TRACK)) {
@@ -623,7 +640,7 @@ public class TrackDataHub implements DataSourceListener {
if (!LocationUtils.isValidLocation(location)) { if (!LocationUtils.isValidLocation(location)) {
// TODO: also include the last valid point before a split // TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) { for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSegmentSplit(); trackDataListener.onSegmentSplit(location);
includeNextPoint = true; includeNextPoint = true;
} }
} else { } else {
@@ -96,12 +96,13 @@ public interface TrackDataListener {
/** /**
* Called when an invalid track point representing a segment split is read. * Called when an invalid track point representing a segment split is read.
*/ */
public void onSegmentSplit(); public void onSegmentSplit(Location location);
/** /**
* Called when finish sending new track points. This gets called after every * Called when finish sending new track points. This gets called after every
* batch of calls to {@link #onSampledInTrackPoint(Location)}, * batch of calls to {@link #onSampledInTrackPoint(Location)},
* {@link #onSampledOutTrackPoint(Location)} and {@link #onSegmentSplit()}. * {@link #onSampledOutTrackPoint(Location)} and
* {@link #onSegmentSplit(Location)}.
*/ */
public void onNewTrackPointsDone(); public void onNewTrackPointsDone();
@@ -139,4 +140,12 @@ public interface TrackDataListener {
* @return true to reload all the data, false otherwise. * @return true to reload all the data, false otherwise.
*/ */
public boolean onReportSpeedChanged(boolean reportSpeed); public boolean onReportSpeedChanged(boolean reportSpeed);
/**
* Called when the min recording distance preference value is changed.
*
* @param minRecordingDistance the new value
* @return true to reload all the data, false otherwise.
*/
public boolean onMinRecordingDistanceChanged(int minRecordingDistance);
} }
@@ -17,7 +17,6 @@
package com.google.android.apps.mytracks.fragments; package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.ChartView; import com.google.android.apps.mytracks.ChartView;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor;
@@ -27,7 +26,7 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener; import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataType; import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
@@ -56,28 +55,22 @@ import java.util.EnumSet;
public class ChartFragment extends Fragment implements TrackDataListener { public class ChartFragment extends Fragment implements TrackDataListener {
public static final String CHART_FRAGMENT_TAG = "chartFragment"; public static final String CHART_FRAGMENT_TAG = "chartFragment";
// Android reports 128 when the speed is invalid
private static final int INVALID_SPEED = 128;
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>(); private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
// Stats gathered from the received data // Stats gathered from the received data
private double totalDistance = 0.0; private TripStatisticsBuilder tripStatisticsBuilder;
private long startTime = -1L; private long startTime;
private Location lastLocation = null;
private double trackMaxSpeed = 0.0; private boolean metricUnits = PreferencesUtils.METRIC_UNITS_DEFAULT;
private boolean reportSpeed = PreferencesUtils.REPORT_SPEED_DEFAULT;
private int minRecordingDistance = PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT;
// Modes of operation // Modes of operation
private boolean metricUnits = true;
private boolean reportSpeed = true;
private boolean chartByDistance = true; private boolean chartByDistance = true;
private boolean[] chartShow = new boolean[] {true, true, true, true, true, true}; private boolean[] chartShow = new boolean[] { true, true, true, true, true, true };
// UI elements // UI elements
private ChartView chartView; private ChartView chartView;
@@ -88,7 +81,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
* appropriate and redraw. * appropriate and redraw.
*/ */
private final Runnable updateChart = new Runnable() { private final Runnable updateChart = new Runnable() {
@Override @Override
public void run() { public void run() {
if (trackDataHub == null) { if (trackDataHub == null) {
return; return;
@@ -118,13 +111,13 @@ public class ChartFragment extends Fragment implements TrackDataListener {
View view = inflater.inflate(R.layout.chart, container, false); View view = inflater.inflate(R.layout.chart, container, false);
zoomControls = (ZoomControls) view.findViewById(R.id.chart_zoom_controls); zoomControls = (ZoomControls) view.findViewById(R.id.chart_zoom_controls);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() { zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
zoomIn(); zoomIn();
} }
}); });
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() { zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
zoomOut(); zoomOut();
} }
@@ -141,7 +134,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.addView(chartView, layoutParams); layout.addView(chartView, layoutParams);
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@@ -186,26 +179,20 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override @Override
public void onTrackUpdated(Track track) { public void onTrackUpdated(Track track) {
if (track == null || track.getTripStatistics() == null) { if (track == null || track.getTripStatistics() == null) {
trackMaxSpeed = 0.0; startTime = -1L;
return; return;
} }
trackMaxSpeed = track.getTripStatistics().getMaxSpeed(); startTime = track.getTripStatistics().getStartTime();
} }
@Override @Override
public void clearTrackPoints() { public void clearTrackPoints() {
totalDistance = 0.0; tripStatisticsBuilder = startTime != -1L ? new TripStatisticsBuilder(startTime) : null;
startTime = -1L;
lastLocation = null;
elevationBuffer.reset();
speedBuffer.reset();
pendingPoints.clear(); pendingPoints.clear();
chartView.reset(); chartView.reset();
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
chartView.resetScroll(); chartView.resetScroll();
} }
@@ -214,24 +201,19 @@ public class ChartFragment extends Fragment implements TrackDataListener {
@Override @Override
public void onSampledInTrackPoint(Location location) { public void onSampledInTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) { double[] data = new double[ChartView.NUM_SERIES + 1];
double[] data = new double[ChartView.NUM_SERIES + 1]; fillDataPoint(location, data);
fillDataPoint(location, data); pendingPoints.add(data);
pendingPoints.add(data);
}
} }
@Override @Override
public void onSampledOutTrackPoint(Location location) { public void onSampledOutTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) { fillDataPoint(location, null);
// Still account for the point in the smoothing buffers.
fillDataPoint(location, null);
}
} }
@Override @Override
public void onSegmentSplit() { public void onSegmentSplit(Location location) {
// Do nothing. fillDataPoint(location, null);
} }
@Override @Override
@@ -266,7 +248,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
metricUnits = metric; metricUnits = metric;
chartView.setMetricUnits(metricUnits); chartView.setMetricUnits(metricUnits);
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
chartView.requestLayout(); chartView.requestLayout();
} }
@@ -286,7 +268,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed); setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed);
setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed); setSeriesEnabled(ChartView.PACE_SERIES, chartShowSpeed && !reportSpeed);
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
chartView.requestLayout(); chartView.requestLayout();
} }
@@ -294,6 +276,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return true; return true;
} }
@Override
public boolean onMinRecordingDistanceChanged(int value) {
if (minRecordingDistance == value) {
return false;
}
minRecordingDistance = value;
return true;
}
/** /**
* Checks the chart settings. * Checks the chart settings.
*/ */
@@ -310,7 +301,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) { R.string.chart_show_elevation_key, PreferencesUtils.CHART_SHOW_ELEVATION_DEFAULT))) {
needUpdate = true; needUpdate = true;
} }
boolean chartShowSpeed = PreferencesUtils.getBoolean( boolean chartShowSpeed = PreferencesUtils.getBoolean(
getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT); getActivity(), R.string.chart_show_speed_key, PreferencesUtils.CHART_SHOW_SPEED_DEFAULT);
if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) { if (setSeriesEnabled(ChartView.SPEED_SERIES, chartShowSpeed && reportSpeed)) {
@@ -354,17 +345,14 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
/** /**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* accessed by multiple threads. * be accessed by multiple threads.
*/ */
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK,
TrackDataType.SELECTED_TRACK, TrackDataType.TRACKS_TABLE, TrackDataType.WAYPOINTS_TABLE,
TrackDataType.TRACKS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE,
TrackDataType.WAYPOINTS_TABLE,
TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE,
TrackDataType.PREFERENCE)); TrackDataType.PREFERENCE));
} }
@@ -386,15 +374,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
} }
/** /**
* Reloads the trackDataHub. Needs to be synchronized because trackDataHub can be * Reloads the trackDataHub. Needs to be synchronized because trackDataHub can
* accessed by multiple threads. * be accessed by multiple threads.
*/ */
private synchronized void reloadTrackDataHub() { private synchronized void reloadTrackDataHub() {
if (trackDataHub != null) { if (trackDataHub != null) {
trackDataHub.reloadDataForListener(this); trackDataHub.reloadDataForListener(this);
} }
} }
/** /**
* To zoom in. * To zoom in.
*/ */
@@ -412,7 +400,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
zoomControls.setIsZoomInEnabled(chartView.canZoomIn()); zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut()); zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
} }
/** /**
* Given a location, fill in a data point, an array of double[]. <br> * Given a location, fill in a data point, an array of double[]. <br>
* data[0] = time/distance <br> * data[0] = time/distance <br>
@@ -435,44 +423,31 @@ public class ChartFragment extends Fragment implements TrackDataListener {
double heartRate = Double.NaN; double heartRate = Double.NaN;
double cadence = Double.NaN; double cadence = Double.NaN;
double power = Double.NaN; double power = Double.NaN;
// TODO: Use TripStatisticsBuilder
if (chartByDistance) {
if (lastLocation != null) {
double distance = lastLocation.distanceTo(location) * UnitConversions.M_TO_KM;
if (metricUnits) {
totalDistance += distance;
} else {
totalDistance += distance * UnitConversions.KM_TO_MI;
}
}
timeOrDistance = totalDistance;
} else {
if (startTime == -1L) {
startTime = location.getTime();
}
timeOrDistance = location.getTime() - startTime;
}
elevationBuffer.setNext(metricUnits ? location.getAltitude() : location.getAltitude()
* UnitConversions.M_TO_FT);
elevation = elevationBuffer.getAverage();
if (lastLocation == null) {
if (Math.abs(location.getSpeed() - INVALID_SPEED) > 1) {
speedBuffer.setNext(location.getSpeed());
}
} else if (TripStatisticsBuilder.isValidSpeed(location.getTime(), location.getSpeed(),
lastLocation.getTime(), lastLocation.getSpeed(), speedBuffer)
&& (location.getSpeed() <= trackMaxSpeed)) {
speedBuffer.setNext(location.getSpeed());
}
speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
if (tripStatisticsBuilder != null) {
tripStatisticsBuilder.addLocation(location, minRecordingDistance);
TripStatistics tripStatistics = tripStatisticsBuilder.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
} else {
timeOrDistance = tripStatistics.getTotalTime();
}
elevation = tripStatisticsBuilder.getSmoothedElevation();
if (!metricUnits) {
elevation *= UnitConversions.M_TO_FT;
}
speed = tripStatisticsBuilder.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
}
if (location instanceof MyTracksLocation if (location instanceof MyTracksLocation
&& ((MyTracksLocation) location).getSensorDataSet() != null) { && ((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet(); SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
@@ -502,7 +477,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
data[5] = cadence; data[5] = cadence;
data[6] = power; data[6] = power;
} }
lastLocation = location;
} }
@VisibleForTesting @VisibleForTesting
@@ -510,25 +484,26 @@ public class ChartFragment extends Fragment implements TrackDataListener {
return chartView; return chartView;
} }
@VisibleForTesting
void setTripStatisticsBuilder(long time) {
tripStatisticsBuilder = new TripStatisticsBuilder(time);
}
@VisibleForTesting @VisibleForTesting
void setChartView(ChartView view) { void setChartView(ChartView view) {
chartView = view; chartView = view;
} }
@VisibleForTesting
void setTrackMaxSpeed(double value) {
trackMaxSpeed = value;
}
@VisibleForTesting @VisibleForTesting
void setMetricUnits(boolean value) { void setMetricUnits(boolean value) {
metricUnits = value; metricUnits = value;
} }
@VisibleForTesting @VisibleForTesting
void setReportSpeed(boolean value) { void setReportSpeed(boolean value) {
reportSpeed = value; reportSpeed = value;
} }
@VisibleForTesting @VisibleForTesting
void setChartByDistance(boolean value) { void setChartByDistance(boolean value) {
chartByDistance = value; chartByDistance = value;
@@ -57,7 +57,7 @@ import java.util.List;
/** /**
* A fragment to display map to the user. * A fragment to display map to the user.
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
@@ -65,7 +65,7 @@ public class MapFragment extends Fragment
implements View.OnTouchListener, View.OnClickListener, TrackDataListener { implements View.OnTouchListener, View.OnClickListener, TrackDataListener {
public static final String MAP_FRAGMENT_TAG = "mapFragment"; public static final String MAP_FRAGMENT_TAG = "mapFragment";
private static final String KEY_CURRENT_LOCATION = "currentLocation"; private static final String KEY_CURRENT_LOCATION = "currentLocation";
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible"; private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
@@ -108,19 +108,19 @@ public class MapFragment extends Fragment
mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer(); mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
mapView = (MapView) mapViewContainer.findViewById(R.id.map_view); mapView = (MapView) mapViewContainer.findViewById(R.id.map_view);
mapOverlay = new MapOverlay(getActivity()); mapOverlay = new MapOverlay(getActivity());
List<Overlay> overlays = mapView.getOverlays(); List<Overlay> overlays = mapView.getOverlays();
overlays.clear(); overlays.clear();
overlays.add(mapOverlay); overlays.add(mapOverlay);
mapView.requestFocus(); mapView.requestFocus();
mapView.setOnTouchListener(this); mapView.setOnTouchListener(this);
mapView.setBuiltInZoomControls(true); mapView.setBuiltInZoomControls(true);
myLocationImageButton = (ImageButton) mapViewContainer.findViewById(R.id.map_my_location); myLocationImageButton = (ImageButton) mapViewContainer.findViewById(R.id.map_my_location);
myLocationImageButton.setOnClickListener(new View.OnClickListener() { myLocationImageButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
showMyLocation(); showMyLocation();
} }
}); });
messageTextView = (TextView) mapViewContainer.findViewById(R.id.map_message); messageTextView = (TextView) mapViewContainer.findViewById(R.id.map_message);
@@ -227,7 +227,7 @@ public class MapFragment extends Fragment
/** /**
* Shows the marker. * Shows the marker.
* *
* @param trackId the track id * @param trackId the track id
* @param id the marker id * @param id the marker id
*/ */
@@ -277,29 +277,36 @@ public class MapFragment extends Fragment
public void onLocationStateChanged(LocationState state) { public void onLocationStateChanged(LocationState state) {
final String message; final String message;
final boolean isGpsDisabled; final boolean isGpsDisabled;
switch (state) { if (!isSelectedTrackRecording()) {
case DISABLED: message = null;
String setting = getString( isGpsDisabled = false;
GoogleLocationUtils.isAvailable(getActivity()) ? R.string.gps_google_location_settings } else {
: R.string.gps_location_access); switch (state) {
message = getString(R.string.gps_disabled, setting); case DISABLED:
isGpsDisabled = true; String setting = getString(
break; GoogleLocationUtils.isAvailable(getActivity()) ? R.string.gps_google_location_settings
case NO_FIX: : R.string.gps_location_access);
case BAD_FIX: message = getString(R.string.gps_disabled, setting);
message = getString(R.string.gps_wait_for_signal); isGpsDisabled = true;
isGpsDisabled = false; break;
break; case NO_FIX:
case GOOD_FIX: message = getString(R.string.gps_wait_for_signal);
message = null; isGpsDisabled = false;
isGpsDisabled = false; break;
break; case BAD_FIX:
default: message = getString(R.string.gps_wait_for_better_signal);
throw new IllegalArgumentException("Unexpected state: " + state); isGpsDisabled = false;
break;
case GOOD_FIX:
message = null;
isGpsDisabled = false;
break;
default:
throw new IllegalArgumentException("Unexpected state: " + state);
}
} }
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (message != null) { if (message != null) {
messageTextView.setText(message); messageTextView.setText(message);
@@ -336,12 +343,12 @@ public class MapFragment extends Fragment
@Override @Override
public void onSelectedTrackChanged(final Track track) { public void onSelectedTrackChanged(final Track track) {
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
boolean hasTrack = track != null; boolean hasTrack = track != null;
mapOverlay.setTrackDrawingEnabled(hasTrack); mapOverlay.setTrackDrawingEnabled(hasTrack);
if (hasTrack) { if (hasTrack) {
synchronized (this) { synchronized (this) {
/* /*
* Synchronize to prevent race condition in changing markerTrackId * Synchronize to prevent race condition in changing markerTrackId
@@ -369,18 +376,16 @@ public class MapFragment extends Fragment
@Override @Override
public void onSampledInTrackPoint(Location location) { public void onSampledInTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) { mapOverlay.addLocation(location);
mapOverlay.addLocation(location);
}
} }
@Override @Override
public void onSampledOutTrackPoint(Location loc) { public void onSampledOutTrackPoint(Location location) {
// We don't care. // We don't care.
} }
@Override @Override
public void onSegmentSplit() { public void onSegmentSplit(Location location) {
mapOverlay.addSegmentSplit(); mapOverlay.addSegmentSplit();
} }
@@ -418,24 +423,27 @@ public class MapFragment extends Fragment
// We don't care. // We don't care.
return false; return false;
} }
@Override
public boolean onMinRecordingDistanceChanged(int minRecordingDistance) {
// We don't care.
return false;
}
/** /**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* accessed by multiple threads. * be accessed by multiple threads.
*/ */
private synchronized void resumeTrackDataHub() { private synchronized void resumeTrackDataHub() {
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of( trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK,
TrackDataType.SELECTED_TRACK, TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.WAYPOINTS_TABLE, TrackDataType.LOCATION, TrackDataType.HEADING));
TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
TrackDataType.LOCATION,
TrackDataType.HEADING));
} }
/** /**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* accessed by multiple threads. * be accessed by multiple threads.
*/ */
private synchronized void pauseTrackDataHub() { private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this); trackDataHub.unregisterTrackDataListener(this);
@@ -443,15 +451,15 @@ public class MapFragment extends Fragment
} }
/** /**
* Updates the trackDataHub. Needs to be synchronized because trackDataHub can be * Updates the trackDataHub. Needs to be synchronized because trackDataHub can
* accessed by multiple threads. * be accessed by multiple threads.
*/ */
private synchronized void updateTrackDataHub() { private synchronized void updateTrackDataHub() {
if (trackDataHub != null) { if (trackDataHub != null) {
trackDataHub.forceUpdateLocation(); trackDataHub.forceUpdateLocation();
} }
} }
/** /**
* Returns true if the selected track is recording. Needs to be synchronized * Returns true if the selected track is recording. Needs to be synchronized
* because trackDataHub can be accessed by multiple threads. * because trackDataHub can be accessed by multiple threads.
@@ -461,8 +469,9 @@ public class MapFragment extends Fragment
} }
/** /**
* Updates the map by either zooming to the requested marker or showing the track. * Updates the map by either zooming to the requested marker or showing the
* * track.
*
* @param track the track * @param track the track
*/ */
private void updateMap(Track track) { private void updateMap(Track track) {
@@ -480,7 +489,7 @@ public class MapFragment extends Fragment
/** /**
* Returns true if the location is visible. * Returns true if the location is visible.
* *
* @param location the location * @param location the location
*/ */
private boolean isVisible(Location location) { private boolean isVisible(Location location) {
@@ -490,7 +499,7 @@ public class MapFragment extends Fragment
GeoPoint mapCenter = mapView.getMapCenter(); GeoPoint mapCenter = mapView.getMapCenter();
int latitudeSpan = mapView.getLatitudeSpan(); int latitudeSpan = mapView.getLatitudeSpan();
int longitudeSpan = mapView.getLongitudeSpan(); int longitudeSpan = mapView.getLongitudeSpan();
/* /*
* The bottom of the mapView is obscured by the zoom controls, subtract its * The bottom of the mapView is obscured by the zoom controls, subtract its
* height from the visible area. * height from the visible area.
@@ -498,11 +507,11 @@ public class MapFragment extends Fragment
GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight()); GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
GeoPoint zoomControlTop = mapView.getProjection().fromPixels( GeoPoint zoomControlTop = mapView.getProjection().fromPixels(
0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight()); 0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6() int zoomControlMargin = Math.abs(
- zoomControlBottom.getLatitudeE6()); zoomControlTop.getLatitudeE6() - zoomControlBottom.getLatitudeE6());
GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan); GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
geoRect.top += zoomControlMargin; geoRect.top += zoomControlMargin;
GeoPoint geoPoint = LocationUtils.getGeoPoint(location); GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
return geoRect.contains(geoPoint); return geoRect.contains(geoPoint);
} }
@@ -534,7 +543,7 @@ public class MapFragment extends Fragment
/** /**
* Shows the track. * Shows the track.
* *
* @param track the track * @param track the track
*/ */
private void showTrack(Track track) { private void showTrack(Track track) {
@@ -186,17 +186,17 @@ public class StatsFragment extends Fragment implements TrackDataListener {
} }
@Override @Override
public void onSampledInTrackPoint(Location loc) { public void onSampledInTrackPoint(Location location) {
// We don't care. // We don't care.
} }
@Override @Override
public void onSampledOutTrackPoint(Location loc) { public void onSampledOutTrackPoint(Location location) {
// We don't care. // We don't care.
} }
@Override @Override
public void onSegmentSplit() { public void onSegmentSplit(Location location) {
// We don't care. // We don't care.
} }
@@ -246,6 +246,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
return true; return true;
} }
@Override
public boolean onMinRecordingDistanceChanged(int minRecordingDistance) {
// We don't care.
return false;
}
/** /**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads. * be accessed by multiple threads.
@@ -10,13 +10,11 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask; import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils; import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import com.google.api.client.googleapis.GoogleHeaders; import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.googleapis.MethodOverride; import com.google.api.client.googleapis.MethodOverride;
@@ -48,7 +46,7 @@ import java.util.Vector;
/** /**
* AsyncTask to send a track to Google Fusion Tables. * AsyncTask to send a track to Google Fusion Tables.
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask { public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
@@ -59,8 +57,8 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
@VisibleForTesting @VisibleForTesting
public static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
@VisibleForTesting @VisibleForTesting
public static final String FUSION_TABLES_BASE_URL = public static final String
"https://www.google.com/fusiontables/api/query"; FUSION_TABLES_BASE_URL = "https://www.google.com/fusiontables/api/query";
private static final int MAX_POINTS_PER_UPLOAD = 2048; private static final int MAX_POINTS_PER_UPLOAD = 2048;
@VisibleForTesting @VisibleForTesting
public static final String GDATA_VERSION = "2"; public static final String GDATA_VERSION = "2";
@@ -72,14 +70,15 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
private static final int PROGRESS_UPLOAD_WAYPOINTS = 95; private static final int PROGRESS_UPLOAD_WAYPOINTS = 95;
private static final int PROGRESS_COMPLETE = 100; private static final int PROGRESS_COMPLETE = 100;
// See http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991 // See
// http://support.google.com/fusiontables/bin/answer.py?hl=en&answer=185991
private static final String MARKER_TYPE_START = "large_green"; private static final String MARKER_TYPE_START = "large_green";
private static final String MARKER_TYPE_END = "large_red"; private static final String MARKER_TYPE_END = "large_red";
private static final String MARKER_TYPE_WAYPOINT = "large_blue"; private static final String MARKER_TYPE_WAYPOINT = "large_blue";
private static final String MARKER_TYPE_STATISTICS = "large_yellow"; private static final String MARKER_TYPE_STATISTICS = "large_yellow";
private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName(); private static final String TAG = SendFusionTablesAsyncTask.class.getSimpleName();
private final Context context; private final Context context;
private final long trackId; private final long trackId;
private final Account account; private final Account account;
@@ -127,8 +126,8 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
currentSegment = 1; currentSegment = 1;
try { try {
authToken = AccountManager.get(context).blockingGetAuthToken( authToken = AccountManager.get(context)
account, SendFusionTablesUtils.SERVICE, false); .blockingGetAuthToken(account, SendFusionTablesUtils.SERVICE, false);
} catch (OperationCanceledException e) { } catch (OperationCanceledException e) {
Log.d(TAG, "Unable to get auth token", e); Log.d(TAG, "Unable to get auth token", e);
return retryTask(); return retryTask();
@@ -182,7 +181,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Creates a new table. * Creates a new table.
* *
* @param track the track * @param track the track
* @return true if success. * @return true if success.
*/ */
@@ -194,7 +193,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Unlists a table. * Unlists a table.
* *
* @return true if success. * @return true if success.
*/ */
private boolean unlistTable() { private boolean unlistTable() {
@@ -204,32 +203,31 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Uploads all the points in a track. * Uploads all the points in a track.
* *
* @param track the track * @param track the track
* @return true if success. * @return true if success.
*/ */
private boolean uploadAllTrackPoints(Track track) { private boolean uploadAllTrackPoints(Track track) {
Cursor locationsCursor = null; Cursor locationsCursor = null;
try { try {
boolean metricUnits = PreferencesUtils.getBoolean(
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false); locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false);
if (locationsCursor == null) { if (locationsCursor == null) {
Log.d(TAG, "Location cursor is null"); Log.d(TAG, "Location cursor is null");
return false; return false;
} }
int locationsCount = locationsCursor.getCount(); int locationsCount = locationsCursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD); List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastLocation = null; Location lastLocation = null;
// For chart server, limit the number of elevation readings to 250. // For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0)); int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0));
TripStatisticsBuilder tripStatisticsBuilder = new TripStatisticsBuilder(
track.getTripStatistics().getStartTime());
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
Vector<Double> distances = new Vector<Double>(); Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>(); Vector<Double> elevations = new Vector<Double>();
TripStatisticsBuilder tripStatisticsBuilder = new TripStatisticsBuilder(
track.getTripStatistics().getStartTime());
int minRecordingDistance = PreferencesUtils.getInt(context,
R.string.min_recording_distance_key, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
for (int i = 0; i < locationsCount; i++) { for (int i = 0; i < locationsCount; i++) {
locationsCursor.moveToPosition(i); locationsCursor.moveToPosition(i);
@@ -246,16 +244,12 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
} }
} }
// Add to the distances and elevations vectors tripStatisticsBuilder.addLocation(location, minRecordingDistance);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(tripStatisticsBuilder.getSmoothedElevation());
}
if (LocationUtils.isValidLocation(location)) { if (LocationUtils.isValidLocation(location)) {
tripStatisticsBuilder.addLocation(location, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
// All points go into the smoothing buffer
elevationBuffer.setNext(metricUnits ? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
}
lastLocation = location; lastLocation = location;
} }
@@ -280,7 +274,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
// Create an end marker // Create an end marker
if (lastLocation != null) { if (lastLocation != null) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance()); distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage()); elevations.add(tripStatisticsBuilder.getSmoothedElevation());
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context); DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
track.setDescription( track.setDescription(
descriptionGenerator.generateTrackDescription(track, distances, elevations, true)); descriptionGenerator.generateTrackDescription(track, distances, elevations, true));
@@ -301,7 +295,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Prepares and uploads a list of locations from a track. * Prepares and uploads a list of locations from a track.
* *
* @param track the track * @param track the track
* @param locations the locations from the track * @param locations the locations from the track
* @param lastBatch true if it is the last batch of locations * @param lastBatch true if it is the last batch of locations
@@ -328,7 +322,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Uploads all the waypoints. * Uploads all the waypoints.
* *
* @return true if success. * @return true if success.
*/ */
private boolean uploadWaypoints() { private boolean uploadWaypoints() {
@@ -358,17 +352,15 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
} }
/** /**
* Creates a new row in Google Fusion Tables representing a marker as a * Creates a new row in Google Fusion Tables representing a marker as a point.
* point. *
*
* @param name the marker name * @param name the marker name
* @param description the marker description * @param description the marker description
* @param location the marker location * @param location the marker location
* @param type the marker type * @param type the marker type
* @return true if success. * @return true if success.
*/ */
private boolean createNewPoint( private boolean createNewPoint(String name, String description, Location location, String type) {
String name, String description, Location location, String type) {
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES " String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
+ SendFusionTablesUtils.formatSqlValues( + SendFusionTablesUtils.formatSqlValues(
name, description, SendFusionTablesUtils.getKmlPoint(location), type); name, description, SendFusionTablesUtils.getKmlPoint(location), type);
@@ -376,22 +368,23 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
} }
/** /**
* Creates a new row in Google Fusion Tables representing the track as a * Creates a new row in Google Fusion Tables representing the track as a line
* line segment. * segment.
* *
* @param track the track * @param track the track
* @return true if success. * @return true if success.
*/ */
private boolean createNewLineString(Track track) { private boolean createNewLineString(Track track) {
String query = "INSERT INTO " + tableId + " (name,description,geometry) VALUES " String query = "INSERT INTO " + tableId
+ SendFusionTablesUtils.formatSqlValues(track.getName(), track.getDescription(), + " (name,description,geometry) VALUES " + SendFusionTablesUtils.formatSqlValues(
track.getName(), track.getDescription(),
SendFusionTablesUtils.getKmlLineString(track.getLocations())); SendFusionTablesUtils.getKmlLineString(track.getLocations()));
return sendQuery(query, false); return sendQuery(query, false);
} }
/** /**
* Sends a query to Google Fusion Tables. * Sends a query to Google Fusion Tables.
* *
* @param query the Fusion Tables SQL query * @param query the Fusion Tables SQL query
* @param setTableId true to set the table id * @param setTableId true to set the table id
* @return true if success. * @return true if success.
@@ -463,7 +456,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
/** /**
* Updates the progress based on the number of locations uploaded. * Updates the progress based on the number of locations uploaded.
* *
* @param uploaded the number of uploaded locations * @param uploaded the number of uploaded locations
* @param total the number of total locations * @param total the number of total locations
*/ */
@@ -474,4 +467,3 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
publishProgress((int) scaledPercentage); publishProgress((int) scaledPercentage);
} }
} }
@@ -28,11 +28,9 @@ import com.google.android.apps.mytracks.io.gdata.maps.MapsGDataConverter;
import com.google.android.apps.mytracks.io.gdata.maps.XmlMapsGDataParserFactory; import com.google.android.apps.mytracks.io.gdata.maps.XmlMapsGDataParserFactory;
import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask; import com.google.android.apps.mytracks.io.sendtogoogle.AbstractSendAsyncTask;
import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils; import com.google.android.apps.mytracks.io.sendtogoogle.SendToGoogleUtils;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.common.gdata.AndroidXmlParserFactory; import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@@ -63,14 +61,14 @@ import org.xmlpull.v1.XmlPullParserException;
* the Google Maps servers through this API is only allowed for the My Tracks * the Google Maps servers through this API is only allowed for the My Tracks
* application. Other applications looking to upload maps data should look into * application. Other applications looking to upload maps data should look into
* using the Google Fusion Tables API. * using the Google Fusion Tables API.
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
public class SendMapsAsyncTask extends AbstractSendAsyncTask { public class SendMapsAsyncTask extends AbstractSendAsyncTask {
private static final String START_ICON_URL = private static final String
"http://maps.google.com/mapfiles/ms/micons/green-dot.png"; START_ICON_URL = "http://maps.google.com/mapfiles/ms/micons/green-dot.png";
private static final String END_ICON_URL = private static final String
"http://maps.google.com/mapfiles/ms/micons/red-dot.png"; END_ICON_URL = "http://maps.google.com/mapfiles/ms/micons/red-dot.png";
private static final int MAX_POINTS_PER_UPLOAD = 500; private static final int MAX_POINTS_PER_UPLOAD = 500;
private static final int PROGRESS_FETCH_MAP_ID = 5; private static final int PROGRESS_FETCH_MAP_ID = 5;
@@ -97,25 +95,25 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
private String mapId; private String mapId;
int currentSegment; int currentSegment;
public SendMapsAsyncTask(SendMapsActivity activity, long trackId, Account account, public SendMapsAsyncTask(
String chooseMapId) { SendMapsActivity activity, long trackId, Account account, String chooseMapId) {
this(activity, trackId, account, chooseMapId, MyTracksProviderUtils.Factory.get(activity this(activity, trackId, account, chooseMapId, MyTracksProviderUtils.Factory.get(
.getApplicationContext())); activity.getApplicationContext()));
} }
/** /**
* This constructor is created for test. * This constructor is created for test.
*/ */
@VisibleForTesting @VisibleForTesting
public SendMapsAsyncTask ( public SendMapsAsyncTask(SendMapsActivity activity, long trackId, Account account,
SendMapsActivity activity, long trackId, Account account, String chooseMapId, MyTracksProviderUtils myTracksProviderUtils) { String chooseMapId, MyTracksProviderUtils myTracksProviderUtils) {
super(activity); super(activity);
this.trackId = trackId; this.trackId = trackId;
this.account = account; this.account = account;
this.chooseMapId = chooseMapId; this.chooseMapId = chooseMapId;
this.myTracksProviderUtils = myTracksProviderUtils; this.myTracksProviderUtils = myTracksProviderUtils;
context = activity.getApplicationContext(); context = activity.getApplicationContext();
gDataClient = GDataClientFactory.getGDataClient(context); gDataClient = GDataClientFactory.getGDataClient(context);
mapsClient = new MapsClient( mapsClient = new MapsClient(
gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory())); gDataClient, new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
@@ -154,11 +152,11 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
Log.d(TAG, "Unable to create a maps gdata converter", e); Log.d(TAG, "Unable to create a maps gdata converter", e);
return false; return false;
} }
// Get auth token // Get auth token
try { try {
authToken = AccountManager.get(context).blockingGetAuthToken( authToken = AccountManager.get(context)
account, MapsConstants.SERVICE_NAME, false); .blockingGetAuthToken(account, MapsConstants.SERVICE_NAME, false);
} catch (OperationCanceledException e) { } catch (OperationCanceledException e) {
Log.d(TAG, "Unable to get auth token", e); Log.d(TAG, "Unable to get auth token", e);
return retryTask(); return retryTask();
@@ -208,9 +206,9 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
} }
/** /**
* Fetches the {@link SendMapsAsyncTask#mapId} instance variable for * Fetches the {@link SendMapsAsyncTask#mapId} instance variable for sending a
* sending a track to Google Maps. * track to Google Maps.
* *
* @param track the Track * @param track the Track
* @return true if able to fetch the mapId variable. * @return true if able to fetch the mapId variable.
*/ */
@@ -219,7 +217,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
if (isCancelled()) { if (isCancelled()) {
return false; return false;
} }
if (chooseMapId != null) { if (chooseMapId != null) {
mapId = chooseMapId; mapId = chooseMapId;
return true; return true;
@@ -247,7 +245,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
/** /**
* Uploads all the points in a track. * Uploads all the points in a track.
* *
* @param track the track * @param track the track
* @return true if success. * @return true if success.
*/ */
@@ -255,25 +253,24 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
boolean uploadAllTrackPoints(Track track) { boolean uploadAllTrackPoints(Track track) {
Cursor locationsCursor = null; Cursor locationsCursor = null;
try { try {
boolean metricUnits = PreferencesUtils.getBoolean(
context, R.string.metric_units_key, PreferencesUtils.METRIC_UNITS_DEFAULT);
locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false); locationsCursor = myTracksProviderUtils.getLocationsCursor(trackId, 0, -1, false);
if (locationsCursor == null) { if (locationsCursor == null) {
Log.d(TAG, "Location cursor is null"); Log.d(TAG, "Location cursor is null");
return false; return false;
} }
int locationsCount = locationsCursor.getCount(); int locationsCount = locationsCursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD); List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastLocation = null; Location lastLocation = null;
// For chart server, limit the number of elevation readings to 250. // For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0)); int elevationSamplingFrequency = Math.max(1, (int) (locationsCount / 250.0));
TripStatisticsBuilder tripStatisticsBuilder = new TripStatisticsBuilder(
track.getTripStatistics().getStartTime());
DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
Vector<Double> distances = new Vector<Double>(); Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>(); Vector<Double> elevations = new Vector<Double>();
TripStatisticsBuilder tripStatisticsBuilder = new TripStatisticsBuilder(
track.getTripStatistics().getStartTime());
int minRecordingDistance = PreferencesUtils.getInt(context,
R.string.min_recording_distance_key, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
for (int i = 0; i < locationsCount; i++) { for (int i = 0; i < locationsCount; i++) {
locationsCursor.moveToPosition(i); locationsCursor.moveToPosition(i);
@@ -290,16 +287,12 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
} }
} }
// Add to the distances and elevations vectors tripStatisticsBuilder.addLocation(location, minRecordingDistance);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(tripStatisticsBuilder.getSmoothedElevation());
}
if (LocationUtils.isValidLocation(location)) { if (LocationUtils.isValidLocation(location)) {
tripStatisticsBuilder.addLocation(location, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
// All points go into the smoothing buffer
elevationBuffer.setNext(metricUnits ? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
if (i % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage());
}
lastLocation = location; lastLocation = location;
} }
@@ -324,9 +317,10 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
// Create an end marker // Create an end marker
if (lastLocation != null) { if (lastLocation != null) {
distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance()); distances.add(tripStatisticsBuilder.getTripStatistics().getTotalDistance());
elevations.add(elevationBuffer.getAverage()); elevations.add(tripStatisticsBuilder.getSmoothedElevation());
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
track.setDescription(getTrackDescription(track, distances, elevations)); track.setDescription(
descriptionGenerator.generateTrackDescription(track, distances, elevations, true));
if (!uploadMarker(context.getString(R.string.marker_label_end, track.getName()), if (!uploadMarker(context.getString(R.string.marker_label_end, track.getName()),
track.getDescription(), END_ICON_URL, lastLocation)) { track.getDescription(), END_ICON_URL, lastLocation)) {
Log.d(TAG, "Unable to create an end marker"); Log.d(TAG, "Unable to create an end marker");
@@ -340,26 +334,10 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
} }
} }
} }
/**
* Gets the description of a track.
*
* @param track the track
* @param distances distance vectors
* @param elevations elevation vectors
* @return the description of a track.
*/
@VisibleForTesting
String getTrackDescription(Track track, Vector<Double> distances, Vector<Double> elevations) {
DescriptionGenerator descriptionGenerator = new DescriptionGeneratorImpl(context);
return "<p>" + track.getDescription() + "</p><p>"
+ descriptionGenerator.generateTrackDescription(track, distances, elevations, true)
+ "</p>";
}
/** /**
* Prepares and uploads a list of locations from a track. * Prepares and uploads a list of locations from a track.
* *
* @param track the track * @param track the track
* @param locations the locations from the track * @param locations the locations from the track
* @param lastBatch true if it is the last batch of locations * @param lastBatch true if it is the last batch of locations
@@ -396,8 +374,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* @return true if success. * @return true if success.
*/ */
@VisibleForTesting @VisibleForTesting
boolean uploadMarker( boolean uploadMarker(String title, String description, String iconUrl, Location location) {
String title, String description, String iconUrl, Location location) {
if (isCancelled()) { if (isCancelled()) {
return false; return false;
} }
@@ -449,10 +426,10 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
} }
return true; return true;
} }
/** /**
* Uploads all the waypoints. * Uploads all the waypoints.
* *
* @return true if success. * @return true if success.
*/ */
@VisibleForTesting @VisibleForTesting
@@ -497,7 +474,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
/** /**
* Updates the progress based on the number of locations uploaded. * Updates the progress based on the number of locations uploaded.
* *
* @param uploaded the number of uploaded locations * @param uploaded the number of uploaded locations
* @param total the number of total locations * @param total the number of total locations
*/ */
@@ -505,11 +482,10 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
void updateProgress(int uploaded, int total) { void updateProgress(int uploaded, int total) {
publishProgress(getPercentage(uploaded, total)); publishProgress(getPercentage(uploaded, total));
} }
/** /**
* Count the percentage of the number of locations uploaded. * Count the percentage of the number of locations uploaded.
* *
* @param uploaded the number of uploaded locations * @param uploaded the number of uploaded locations
* @param total the number of total locations * @param total the number of total locations
*/ */
@@ -520,7 +496,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN; * (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
return (int) scaledPercentage; return (int) scaledPercentage;
} }
/** /**
* Gets the mapID. * Gets the mapID.
* *
@@ -530,7 +506,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
String getMapId() { String getMapId() {
return mapId; return mapId;
} }
/** /**
* Sets the value of mapsGDataConverter. * Sets the value of mapsGDataConverter.
* *
@@ -285,7 +285,7 @@ public class TrackRecordingService extends Service {
// Require announcementExecutor and splitExecutor to be created. // Require announcementExecutor and splitExecutor to be created.
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
timer.schedule(checkLocationListener, 5 * ONE_MINUTE, ONE_MINUTE); timer.schedule(checkLocationListener, 0, ONE_MINUTE);
/* /*
* Try to restart the previous recording track in case the service has been * Try to restart the previous recording track in case the service has been
@@ -843,7 +843,7 @@ public class TrackRecordingService extends Service {
double distanceToLastTrackLocation = location.distanceTo(lastTrackLocation); double distanceToLastTrackLocation = location.distanceTo(lastTrackLocation);
if (distanceToLastTrackLocation < minRecordingDistance && sensorDataSet == null) { if (distanceToLastTrackLocation < minRecordingDistance && sensorDataSet == null) {
// skip Log.d(TAG, "Not recording location due to min recording distance.");
} else if (distanceToLastTrackLocation > maxRecordingDistance) { } else if (distanceToLastTrackLocation > maxRecordingDistance) {
insertLocation(track, lastLocation, lastTrackLocation); insertLocation(track, lastLocation, lastTrackLocation);
Location pause = new Location(LocationManager.GPS_PROVIDER); Location pause = new Location(LocationManager.GPS_PROVIDER);
@@ -873,9 +873,11 @@ public class TrackRecordingService extends Service {
private void insertLocation(Track track, Location location, Location lastRecordedLocation) { private void insertLocation(Track track, Location location, Location lastRecordedLocation) {
if (location == null) { if (location == null) {
Log.w(TAG, "Ignore insertLocation. loation is null.");
return; return;
} }
if (location.equals(lastRecordedLocation)) { if (location.equals(lastRecordedLocation)) {
Log.w(TAG, "Ignore insertLocation. loation same as last recorded location.");
return; return;
} }
@@ -145,11 +145,14 @@ public class TripStatisticsBuilder {
* is noisy so the smoothed elevation is better than the raw elevation for * is noisy so the smoothed elevation is better than the raw elevation for
* many tasks. * many tasks.
*/ */
@VisibleForTesting public double getSmoothedElevation() {
double getSmoothedElevation() {
return elevationBuffer.getAverage(); return elevationBuffer.getAverage();
} }
public double getSmoothedSpeed() {
return speedBuffer.getAverage();
}
/** /**
* Updates a speed reading. Assumes the user is moving. * Updates a speed reading. Assumes the user is moving.
* *
@@ -160,7 +163,7 @@ public class TripStatisticsBuilder {
*/ */
@VisibleForTesting @VisibleForTesting
void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) { void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
if (!isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed, speedBuffer)) { if (!isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) {
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed); Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed);
return; return;
} }
@@ -168,10 +171,6 @@ public class TripStatisticsBuilder {
if (speed > currentSegment.getMaxSpeed()) { if (speed > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(speed); currentSegment.setMaxSpeed(speed);
} }
double movingSpeed = currentSegment.getAverageMovingSpeed();
if (speedBuffer.isFull() && movingSpeed > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(movingSpeed);
}
} }
/** /**
@@ -229,10 +228,9 @@ public class TripStatisticsBuilder {
* @param speed the speed * @param speed the speed
* @param lastLocationTime the last location time * @param lastLocationTime the last location time
* @param lastLocationSpeed the last location speed * @param lastLocationSpeed the last location speed
* @param speedBuffer a buffer of speed readings
*/ */
public static boolean isValidSpeed(long time, double speed, long lastLocationTime, private boolean isValidSpeed(
double lastLocationSpeed, DoubleBuffer speedBuffer) { long time, double speed, long lastLocationTime, double lastLocationSpeed) {
/* /*
* There are a lot of noisy speed readings. Do the cheapest checks first, * There are a lot of noisy speed readings. Do the cheapest checks first,
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context; import android.content.Context;
@@ -31,11 +32,9 @@ import java.util.Vector;
*/ */
public class ChartURLGenerator { public class ChartURLGenerator {
private static final String CHARTS_BASE_URL = private static final String CHARTS_BASE_URL = "http://chart.apis.google.com/chart?";
"http://chart.apis.google.com/chart?";
private ChartURLGenerator() { private ChartURLGenerator() {}
}
/** /**
* Gets a chart of a track. * Gets a chart of a track.
@@ -54,8 +53,7 @@ public class ChartURLGenerator {
} }
/** /**
* Gets a chart of a track. * Gets a chart of a track. This form is for testing without contexts.
* This form is for testing without contexts.
* *
* @param distances An array of distance measurements * @param distances An array of distance measurements
* @param elevations A matching array of elevation measurements * @param elevations A matching array of elevation measurements
@@ -63,9 +61,9 @@ public class ChartURLGenerator {
* @param title The title for the chart * @param title The title for the chart
* @param metricUnits Should the data be displayed in metric units * @param metricUnits Should the data be displayed in metric units
*/ */
public static String getChartUrl( @VisibleForTesting
Vector<Double> distances, Vector<Double> elevations, static String getChartUrl(Vector<Double> distances, Vector<Double> elevations, Track track,
Track track, String title, boolean metricUnits) { String title, boolean metricUnits) {
if (distances == null || elevations == null || track == null) { if (distances == null || elevations == null || track == null) {
return null; return null;
} }
@@ -75,21 +73,23 @@ public class ChartURLGenerator {
} }
// Round it up. // Round it up.
TripStatistics stats = track.getTripStatistics(); TripStatistics tripStatistics = track.getTripStatistics();
double effectiveMaxY = metricUnits double effectiveMaxY = tripStatistics.getMaxElevation();
? stats.getMaxElevation() if (!metricUnits) {
: stats.getMaxElevation() * UnitConversions.M_TO_FT; effectiveMaxY *= UnitConversions.M_TO_FT;
}
effectiveMaxY = ((int) (effectiveMaxY / 100)) * 100 + 100; effectiveMaxY = ((int) (effectiveMaxY / 100)) * 100 + 100;
// Round it down.
double effectiveMinY = 0;
double minElevation = metricUnits
? stats.getMinElevation()
: stats.getMinElevation() * UnitConversions.M_TO_FT;
effectiveMinY = ((int) (minElevation / 100)) * 100; // Round it down.
if (stats.getMinElevation() < 0) { double effectiveMinY = tripStatistics.getMinElevation();
if (!metricUnits) {
effectiveMinY *= UnitConversions.M_TO_FT;
}
effectiveMinY = ((int) (effectiveMinY / 100)) * 100;
if (tripStatistics.getMinElevation() < 0) {
effectiveMinY -= 100; effectiveMinY -= 100;
} }
double ySpread = effectiveMaxY - effectiveMinY; double ySpread = effectiveMaxY - effectiveMinY;
StringBuilder sb = new StringBuilder(CHARTS_BASE_URL); StringBuilder sb = new StringBuilder(CHARTS_BASE_URL);
@@ -102,17 +102,18 @@ public class ChartURLGenerator {
// Labels // Labels
sb.append("&chxt=x,y"); sb.append("&chxt=x,y");
double distKM = stats.getTotalDistance() * UnitConversions.M_TO_KM; double totalDistance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distDisplay = if (!metricUnits) {
metricUnits ? distKM : (distKM * UnitConversions.KM_TO_MI); totalDistance *= UnitConversions.KM_TO_MI;
int xInterval = ((int) (distDisplay / 6)); }
int xInterval = ((int) (totalDistance / 6));
int yInterval = ((int) (ySpread / 600)) * 100; int yInterval = ((int) (ySpread / 600)) * 100;
if (yInterval < 100) { if (yInterval < 100) {
yInterval = 25; yInterval = 25;
} }
// Range // Range
sb.append("&chxr=0,0,"); sb.append("&chxr=0,0,");
sb.append((int) distDisplay); sb.append((int) totalDistance);
sb.append(','); sb.append(',');
sb.append(xInterval); sb.append(xInterval);
@@ -138,28 +139,27 @@ public class ChartURLGenerator {
// Data // Data
sb.append("&chd=e:"); sb.append("&chd=e:");
for (int i = 0; i < distances.size(); i++) { for (int i = 0; i < distances.size(); i++) {
int normalized = int normalized = (int) (getNormalizedDistance(distances.elementAt(i), track) * 4095);
(int) (getNormalizedDistance(distances.elementAt(i), track) * 4095);
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized)); sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
} }
sb.append(ChartsExtendedEncoder.getSeparator()); sb.append(ChartsExtendedEncoder.getSeparator());
for (int i = 0; i < elevations.size(); i++) { for (int i = 0; i < elevations.size(); i++) {
int normalized = double value = elevations.elementAt(i);
(int) (getNormalizedElevation( if (!metricUnits) {
elevations.elementAt(i), effectiveMinY, ySpread) * 4095); value *= UnitConversions.M_TO_FT;
}
int normalized = (int) (getNormalizedElevation(value, effectiveMinY, ySpread) * 4095);
sb.append(ChartsExtendedEncoder.getEncodedValue(normalized)); sb.append(ChartsExtendedEncoder.getEncodedValue(normalized));
} }
return sb.toString(); return sb.toString();
} }
private static double getNormalizedDistance(double d, Track track) { private static double getNormalizedDistance(double value, Track track) {
return d / track.getTripStatistics().getTotalDistance(); return value / track.getTripStatistics().getTotalDistance();
} }
private static double getNormalizedElevation( private static double getNormalizedElevation(double value, double effectiveMinY, double ySpread) {
double d, double effectiveMinY, double ySpread) { return (value - effectiveMinY) / ySpread;
return (d - effectiveMinY) / ySpread;
} }
} }
@@ -36,6 +36,7 @@ public class TrackStubUtils {
public static final double INITIAL_ALTITUDE = 22; public static final double INITIAL_ALTITUDE = 22;
static final float INITIAL_ACCURACY = 5; static final float INITIAL_ACCURACY = 5;
static final float INITIAL_SPEED = 10; static final float INITIAL_SPEED = 10;
public static final long INITIAL_TIME = 1000L;
static final float INITIAL_BEARING = 3.0f; static final float INITIAL_BEARING = 3.0f;
// Used to change the value of latitude, longitude, and altitude. // Used to change the value of latitude, longitude, and altitude.
static final double DIFFERENCE = 0.01; static final double DIFFERENCE = 0.01;
@@ -79,7 +80,7 @@ public class TrackStubUtils {
loc.setAltitude(altitude); loc.setAltitude(altitude);
loc.setAccuracy(INITIAL_ACCURACY); loc.setAccuracy(INITIAL_ACCURACY);
loc.setSpeed(INITIAL_SPEED); loc.setSpeed(INITIAL_SPEED);
loc.setTime(System.currentTimeMillis()); loc.setTime(INITIAL_TIME);
loc.setBearing(INITIAL_BEARING); loc.setBearing(INITIAL_BEARING);
SensorDataSet sd = SensorDataSet.newBuilder().build(); SensorDataSet sd = SensorDataSet.newBuilder().build();
MyTracksLocation myTracksLocation = new MyTracksLocation(loc, sd); MyTracksLocation myTracksLocation = new MyTracksLocation(loc, sd);
@@ -613,12 +613,18 @@ public class TrackDataHubTest extends AndroidTestCase {
// Register two listeners // Register two listeners
PreferencesUtils.setBoolean(context, R.string.report_speed_key, true); PreferencesUtils.setBoolean(context, R.string.report_speed_key, true);
PreferencesUtils.setBoolean(context, R.string.metric_units_key, true); PreferencesUtils.setBoolean(context, R.string.metric_units_key, true);
PreferencesUtils.setInt(context, R.string.min_recording_distance_key,
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceChangeListenerCapture)); dataSource.registerOnSharedPreferenceChangeListener(capture(preferenceChangeListenerCapture));
expect(trackDataListener1.onMetricUnitsChanged(true)).andReturn(false); expect(trackDataListener1.onMetricUnitsChanged(true)).andReturn(false);
expect(trackDataListener1.onReportSpeedChanged(true)).andReturn(false); expect(trackDataListener1.onReportSpeedChanged(true)).andReturn(false);
expect(trackDataListener1.onMinRecordingDistanceChanged(
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT)).andReturn(false);
expect(trackDataListener2.onMetricUnitsChanged(true)).andReturn(false); expect(trackDataListener2.onMetricUnitsChanged(true)).andReturn(false);
expect(trackDataListener2.onReportSpeedChanged(true)).andReturn(false); expect(trackDataListener2.onReportSpeedChanged(true)).andReturn(false);
expect(trackDataListener2.onMinRecordingDistanceChanged(
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT)).andReturn(false);
replay(); replay();
trackDataHub.start(); trackDataHub.start();
@@ -737,7 +743,7 @@ public class TrackDataHubTest extends AndroidTestCase {
public void expectLocationsDelivered(TrackDataListener listener) { public void expectLocationsDelivered(TrackDataListener listener) {
for (int i = 0; i < locations.length; i++) { for (int i = 0; i < locations.length; i++) {
if (splitIndexSet.contains(i)) { if (splitIndexSet.contains(i)) {
listener.onSegmentSplit(); listener.onSegmentSplit(locations[i]);
} else { } else {
listener.onSampledInTrackPoint(locations[i]); listener.onSampledInTrackPoint(locations[i]);
} }
@@ -749,7 +755,7 @@ public class TrackDataHubTest extends AndroidTestCase {
boolean includeNext = false; boolean includeNext = false;
for (int i = 0; i < locations.length; i++) { for (int i = 0; i < locations.length; i++) {
if (splitIndexSet.contains(i)) { if (splitIndexSet.contains(i)) {
listener.onSegmentSplit(); listener.onSegmentSplit(locations[i]);
includeNext = true; includeNext = true;
} else if (includeNext || (i % sampleFrequency == 0)) { } else if (includeNext || (i % sampleFrequency == 0)) {
listener.onSampledInTrackPoint(locations[i]); listener.onSampledInTrackPoint(locations[i]);
@@ -41,6 +41,7 @@ public class ChartFragmentTest extends AndroidTestCase {
protected void setUp() throws Exception { protected void setUp() throws Exception {
chartFragment = new ChartFragment(); chartFragment = new ChartFragment();
chartFragment.setChartView(new ChartView(getContext())); chartFragment.setChartView(new ChartView(getContext()));
chartFragment.setTripStatisticsBuilder(TrackStubUtils.INITIAL_TIME);
} }
/** /**
@@ -251,15 +252,12 @@ public class ChartFragmentTest extends AndroidTestCase {
* one. The speed values of these points are 129, 130. * one. The speed values of these points are 129, 130.
*/ */
public void testFillDataPoint_speed() { public void testFillDataPoint_speed() {
// Set max speed to make the speed of points are valid.
chartFragment.setTrackMaxSpeed(200.0);
/* /*
* At first, clear old points of speed, so give true to the second * At first, clear old points of speed, so give true to the second
* parameter. It will not be filled in to the speed buffer. * parameter. It will not be filled in to the speed buffer.
*/ */
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation(); MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
myTracksLocation1.setSpeed(129); myTracksLocation1.setSpeed(128.5f);
double[] point = fillDataPointTestHelper(myTracksLocation1); double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]); assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
@@ -285,10 +283,27 @@ public class ChartFragmentTest extends AndroidTestCase {
public void testFillDataPoint_speedImperial() { public void testFillDataPoint_speedImperial() {
// Setups to use imperial. // Setups to use imperial.
chartFragment.setMetricUnits(false); chartFragment.setMetricUnits(false);
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(132); // First data point is not added to the speed buffer
double[] point = fillDataPointTestHelper(myTracksLocation); MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, myTracksLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
/*
* Add a time span here to make sure the second point is valid and the speed is valid.
*
* Speed is valid if:
* speedDifference > Constants.MAX_ACCELERATION * timeDifference
* speedDifference = 102 -100
* timeDifference = 222
*/
myTracksLocation2.setTime(myTracksLocation2.getTime() + 222);
myTracksLocation2.setSpeed(102);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI,
point[ChartView.SPEED_SERIES + 1]); point[ChartView.SPEED_SERIES + 1]);
} }
@@ -298,11 +313,28 @@ public class ChartFragmentTest extends AndroidTestCase {
public void testFillDataPoint_pace_nonZeroSpeed() { public void testFillDataPoint_pace_nonZeroSpeed() {
// Setups reportSpeed to false. // Setups reportSpeed to false.
chartFragment.setReportSpeed(false); chartFragment.setReportSpeed(false);
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(134); // First data point is not added to the speed buffer
double[] point = fillDataPointTestHelper(myTracksLocation); MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
myTracksLocation1.setSpeed(100.0f);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[ChartView.SPEED_SERIES + 1]);
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
/*
* Add a time span here to make sure the second point is valid and the speed is valid.
*
* Speed is valid if:
* speedDifference > Constants.MAX_ACCELERATION * timeDifference
* speedDifference = 102 -100
* timeDifference = 222
*/
myTracksLocation2.setTime(myTracksLocation2.getTime() + 222);
myTracksLocation2.setSpeed(102);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals( assertEquals(
HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]); HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1]);
} }
/** /**