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