Add support for pause and resume in TrackRecordingService.

This commit is contained in:
Jimmy Shih
2012-08-21 18:36:04 -07:00
parent 832e2499c9
commit ccaf652c8b
13 changed files with 527 additions and 477 deletions
@@ -241,7 +241,8 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
splitFrequencyMenuItem = menu.findItem(R.id.track_detail_split_frequency);
sendGoogleMenuItem = menu.findItem(R.id.track_detail_send_google);
saveMenuItem = menu.findItem(R.id.track_detail_save);
updateMenuItems(trackId == recordingTrackId);
return true;
}
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.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.StringUtils;
import android.location.Location;
@@ -300,11 +301,11 @@ public class GpxImporter extends DefaultHandler {
flushPoints();
// Calculate statistics for the imported track
tripStatisticsBuilder.pauseAt(lastLocation.getTime());
tripStatisticsBuilder.updateTime(lastLocation.getTime());
track.setStopId(getLastPointId());
} else {
tripStatisticsBuilder = new TripStatisticsBuilder(0);
tripStatisticsBuilder.pauseAt(0);
tripStatisticsBuilder = new TripStatisticsBuilder(0L);
tripStatisticsBuilder.updateTime(0L);
}
track.setTripStatistics(tripStatisticsBuilder.getTripStatistics());
track.setNumberOfPoints(numberOfLocations);
@@ -380,7 +381,7 @@ public class GpxImporter extends DefaultHandler {
// first point did not have a time, start stats builder without it
tripStatisticsBuilder = new TripStatisticsBuilder(0);
}
tripStatisticsBuilder.addLocation(location, location.getTime());
tripStatisticsBuilder.addLocation(location, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
// insert in db
insertPoint(location);
@@ -248,7 +248,7 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
// Add to the distances and elevations vectors
if (LocationUtils.isValidLocation(location)) {
tripStatisticsBuilder.addLocation(location, location.getTime());
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);
@@ -292,7 +292,7 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
// Add to the distances and elevations vectors
if (LocationUtils.isValidLocation(location)) {
tripStatisticsBuilder.addLocation(location, location.getTime());
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);
@@ -17,6 +17,7 @@
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.util.GoogleLocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.content.ContentResolver;
import android.content.Context;
@@ -44,7 +45,8 @@ public class MyTracksLocationManager {
private static final String USE_LOCATION_FOR_SERVICES = "use_location_for_services";
// User has agreed to use location for Google services.
private static final String USE_LOCATION_FOR_SERVICES_ON = "1";
@VisibleForTesting
static final String USE_LOCATION_FOR_SERVICES_ON = "1";
private static final String NAME = "name";
private static final String VALUE = "value";
@@ -82,6 +82,9 @@ import java.util.concurrent.Executors;
public class TrackRecordingService extends Service {
private static final String TAG = TrackRecordingService.class.getSimpleName();
public static final int PAUSE_LATITUDE = 100;
private static final int RESUME_LATITUDE = 200;
// One second in milliseconds
private static final long ONE_SECOND = 1000;
// One minute in milliseconds
@@ -105,25 +108,14 @@ public class TrackRecordingService extends Service {
private int minRequiredAccuracy;
private int autoResumeTrackTimeout;
private long currentRecordingInterval;
private Track recordingTrack;
// The following variables are set when recording:
/*
* Track length. Calculated from the recorded points to overlay waypoints
* precisely on the elevation chart.
*/
private double length;
// Used for length calculation
private Location lastLengthLocation;
private TripStatisticsBuilder trackTripStatisticsBuilder;
private TripStatisticsBuilder markerTripStatisticsBuilder;
private WakeLock wakeLock;
private Location lastLocation;
private boolean isMoving;
private SensorManager sensorManager;
private Location lastLocation;
private boolean currentSegmentHasLocation;
// Timer to periodically invoke checkLocationListener
private final Timer timer = new Timer();
@@ -202,10 +194,6 @@ public class TrackRecordingService extends Service {
minRecordingDistance = PreferencesUtils.getInt(context,
R.string.min_recording_distance_key,
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
if (trackTripStatisticsBuilder != null && markerTripStatisticsBuilder != null) {
trackTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
markerTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
}
}
if (key == null || key.equals(
PreferencesUtils.getKey(context, R.string.max_recording_distance_key))) {
@@ -262,10 +250,9 @@ public class TrackRecordingService extends Service {
private TimerTask checkLocationListener = new TimerTask() {
@Override
public void run() {
if (isRecording()) {
if (isRecording() && !isPaused()) {
handler.post(new Runnable() {
public void run() {
unregisterLocationListener();
registerLocationListener();
}
});
@@ -298,21 +285,18 @@ public class TrackRecordingService extends Service {
// Require announcementExecutor and splitExecutor to be created.
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
// Require locationManager and all preferences set.
registerLocationListener();
timer.schedule(checkLocationListener, 5 * ONE_MINUTE, ONE_MINUTE);
/*
* Try to restart the previous recording track in case the service has been
* restarted by the system, which can sometimes happen.
*/
recordingTrack = myTracksProviderUtils.getTrack(recordingTrackId);
if (recordingTrack != null) {
restartTrack(recordingTrack);
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track != null) {
restartTrack(track);
} else {
if (recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
Log.w(TAG, "recordingTrackId not -1L, but recordingTrack is null. " + recordingTrackId);
if (isRecording()) {
Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId);
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
}
showNotification();
@@ -426,11 +410,11 @@ public class TrackRecordingService extends Service {
* @return the waypoint id
*/
public long insertWaypoint(WaypointCreationRequest waypointCreationRequest) {
if (!isRecording()) {
throw new IllegalStateException("Cannot insert marker when not recording!");
if (!isRecording() || isPaused()) {
return -1L;
}
boolean isStatistics = waypointCreationRequest.getType() == WaypointType.STATISTICS;
boolean isStatistics = waypointCreationRequest.getType() == WaypointType.STATISTICS;
String name;
if (waypointCreationRequest.getName() != null) {
name = waypointCreationRequest.getName();
@@ -449,7 +433,7 @@ public class TrackRecordingService extends Service {
String description;
if (isStatistics) {
long now = System.currentTimeMillis();
markerTripStatisticsBuilder.pauseAt(now);
markerTripStatisticsBuilder.updateTime(now);
tripStatistics = markerTripStatisticsBuilder.getTripStatistics();
markerTripStatisticsBuilder = new TripStatisticsBuilder(now);
description = new DescriptionGeneratorImpl(this).generateWaypointDescription(tripStatistics);
@@ -466,24 +450,27 @@ public class TrackRecordingService extends Service {
String icon = getString(
isStatistics ? R.string.marker_statistics_icon_url : R.string.marker_waypoint_icon_url);
int type = isStatistics ? Waypoint.TYPE_STATISTICS : Waypoint.TYPE_WAYPOINT;
long duration;
Location location = myTracksProviderUtils.getLastTrackLocation(recordingTrackId);
if (location != null && trackTripStatisticsBuilder != null
&& trackTripStatisticsBuilder.getTripStatistics() != null) {
duration = location.getTime() - trackTripStatisticsBuilder.getTripStatistics().getStartTime();
double length;
Location lastTrackLocation = myTracksProviderUtils.getLastTrackLocation(recordingTrackId);
if (currentSegmentHasLocation && LocationUtils.isValidLocation(lastTrackLocation)
&& trackTripStatisticsBuilder != null) {
TripStatistics stats = trackTripStatisticsBuilder.getTripStatistics();
length = stats.getTotalDistance();
duration = stats.getTotalTime();
} else {
if (!waypointCreationRequest.isTrackStatistics()) {
return -1L;
}
// For track statistics, make it an impossible location
location = new Location("");
location.setLatitude(100);
location.setLongitude(180);
lastTrackLocation = new Location("");
lastTrackLocation.setLatitude(100);
lastTrackLocation.setLongitude(180);
length = 0;
duration = 0;
}
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, type,
length, duration, -1L, -1L, location, tripStatistics);
length, duration, -1L, -1L, lastTrackLocation, tripStatistics);
Uri uri = myTracksProviderUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
}
@@ -515,7 +502,7 @@ public class TrackRecordingService extends Service {
private void handleStartCommand(Intent intent, int startId) {
// Check if the service is called to resume track (from phone reboot)
if (intent != null && intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
if (!shouldResumeTrack(recordingTrack)) {
if (!shouldResumeTrack()) {
Log.i(TAG, "Stop resume track.");
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
stopSelfResult(startId);
@@ -526,10 +513,10 @@ public class TrackRecordingService extends Service {
/**
* Returns true if should resume.
*
* @param track the track
*/
private boolean shouldResumeTrack(Track track) {
private boolean shouldResumeTrack() {
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track == null) {
Log.d(TAG, "Not resuming. Track is null.");
return false;
@@ -570,32 +557,28 @@ public class TrackRecordingService extends Service {
return -1L;
}
long now = System.currentTimeMillis();
length = 0;
lastLengthLocation = null;
trackTripStatisticsBuilder = new TripStatisticsBuilder(now);
trackTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
markerTripStatisticsBuilder = new TripStatisticsBuilder(now);
markerTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
// Insert a track
Track track = new Track();
track.setStartId(-1L);
TripStatistics tripStatistics = track.getTripStatistics();
tripStatistics.setStartTime(now);
Uri uri = myTracksProviderUtils.insertTrack(track);
long trackId = Long.parseLong(uri.getLastPathSegment());
updateRecordingState(Long.parseLong(uri.getLastPathSegment()), false);
// Update shared preferences
updateRecordingState(trackId, false);
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0);
// Update database
track.setId(recordingTrackId);
track.setName(TrackNameUtils.getTrackName(this, recordingTrackId, now, null));
track.setCategory(PreferencesUtils.getString(
this, R.string.default_activity_key, PreferencesUtils.DEFAULT_ACTIVITY_DEFAULT));
track.setTripStatistics(trackTripStatisticsBuilder.getTripStatistics());
myTracksProviderUtils.updateTrack(track);
insertWaypoint(WaypointCreationRequest.DEFAULT_START_TRACK);
PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0);
registerLocationListener();
startRecording();
startRecording(true);
return recordingTrackId;
}
@@ -607,11 +590,8 @@ public class TrackRecordingService extends Service {
private void restartTrack(Track track) {
Log.d(TAG, "Restarting track: " + track.getId());
length = 0;
lastLengthLocation = null;
TripStatistics tripStatistics = track.getTripStatistics();
trackTripStatisticsBuilder = new TripStatisticsBuilder(tripStatistics.getStartTime());
trackTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
long markerStartTime;
Waypoint waypoint = myTracksProviderUtils.getLastStatisticsWaypoint(recordingTrackId);
@@ -621,10 +601,10 @@ public class TrackRecordingService extends Service {
markerStartTime = tripStatistics.getStartTime();
}
markerTripStatisticsBuilder = new TripStatisticsBuilder(markerStartTime);
markerTripStatisticsBuilder.setMinRecordingDistance(minRecordingDistance);
Cursor cursor = null;
try {
// TODO: how to handle very long track.
cursor = myTracksProviderUtils.getLocationsCursor(
recordingTrackId, -1, Constants.MAX_LOADED_TRACK_POINTS, true);
if (cursor == null) {
@@ -633,20 +613,12 @@ public class TrackRecordingService extends Service {
if (cursor.moveToLast()) {
do {
Location location = myTracksProviderUtils.createLocation(cursor);
if (LocationUtils.isValidLocation(location)) {
trackTripStatisticsBuilder.addLocation(location, location.getTime());
if (location.getTime() > markerStartTime) {
markerTripStatisticsBuilder.addLocation(location, location.getTime());
}
if (lastLengthLocation != null) {
length += location.distanceTo(lastLengthLocation);
}
lastLengthLocation = location;
trackTripStatisticsBuilder.addLocation(location, minRecordingDistance);
if (location.getTime() > markerStartTime) {
markerTripStatisticsBuilder.addLocation(location, minRecordingDistance);
}
} while (cursor.moveToPrevious());
}
trackTripStatisticsBuilder.pauseAt(tripStatistics.getStopTime());
trackTripStatisticsBuilder.resumeAt(System.currentTimeMillis());
}
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
@@ -655,34 +627,59 @@ public class TrackRecordingService extends Service {
cursor.close();
}
}
// TODO: update recordingTrackPaused variable based on Track Points table state
startRecording();
startRecording(true);
}
/**
* Common code for starting a recording, new track or restart track.
* Resumes current track.
*/
private void startRecording() {
private void resumeCurrentTrack() {
if (!isRecording() || !isPaused()) {
Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
return;
}
// Update shared preferences
recordingTrackPaused = false;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, false);
// Update database
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setLongitude(0);
resume.setLatitude(RESUME_LATITUDE);
resume.setTime(System.currentTimeMillis());
insertLocation(resume, null, null);
startRecording(false);
}
/**
* Common code for starting a new track, resuming a track, or restarting after
* phone reboot.
*
* @param trackStarted true if track is started, false if track is resumed
*/
private void startRecording(boolean trackStarted) {
acquireWakeLock();
lastLocation = null;
isMoving = true;
// Update instance variables
sensorManager = SensorManagerFactory.getSystemSensorManager(this);
lastLocation = null;
currentSegmentHasLocation = false;
// Register notifications
registerLocationListener();
// Send notifications
showNotification();
sendTrackBroadcast(R.string.track_started_broadcast_action, recordingTrackId);
sendTrackBroadcast(trackStarted ? R.string.track_started_broadcast_action
: R.string.track_resumed_broadcast_action, recordingTrackId);
// Restore periodic tasks
announcementExecutor.restore();
splitExecutor.restore();
}
private void pauseCurrentTrack() {
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, true);
}
private void resumeCurrentTrack() {
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, false);
}
/**
* Ends the current track.
*/
@@ -691,30 +688,81 @@ public class TrackRecordingService extends Service {
Log.d(TAG, "Ignore endCurrentTrack. Not recording.");
return;
}
announcementExecutor.shutdown();
splitExecutor.shutdown();
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track != null) {
long lastLocationId = myTracksProviderUtils.getLastTrackLocationId(recordingTrackId);
if (lastLocationId >= 0 && track.getStopId() >= 0) {
track.setStopId(lastLocationId);
}
updateTripStatisticsToTime(track.getTripStatistics(), System.currentTimeMillis());
myTracksProviderUtils.updateTrack(track);
}
// Need to remember the recordingTrackId before setting it to -1L
long trackId = recordingTrackId;
// Update shared preferences
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
// Update database
insertLocation(lastLocation, getSensorDataSet(),
myTracksProviderUtils.getLastTrackLocation(recordingTrackId));
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track != null) {
updateRecordingTrack(
track, myTracksProviderUtils.getLastTrackLocationId(recordingTrackId), false);
}
endRecording(true, trackId);
stopSelf();
}
/**
* Pauses the current track.
*/
private void pauseCurrentTrack() {
if (!isRecording() || isPaused()) {
Log.d(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
return;
}
// Update shared preferences
recordingTrackPaused = true;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, true);
// Update database
insertLocation(lastLocation, getSensorDataSet(),
myTracksProviderUtils.getLastTrackLocation(recordingTrackId));
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(PAUSE_LATITUDE);
pause.setTime(System.currentTimeMillis());
insertLocation(pause, null, null);
endRecording(false, recordingTrackId);
}
/**
* Common code for ending a track or pausing a track.
*
* @param trackStopped true if track is stopped, false if track is paused
* @param trackId
*/
private void endRecording(boolean trackStopped, long trackId) {
// Shutdown periodic tasks
announcementExecutor.shutdown();
splitExecutor.shutdown();
// Update instance variables
if (sensorManager != null) {
SensorManagerFactory.releaseSystemSensorManager();
sensorManager = null;
}
lastLocation = null;
// Unregister notifications
unregisterLocationListener();
// Send notifications
showNotification();
sendTrackBroadcast(trackStopped ? R.string.track_stopped_broadcast_action
: R.string.track_paused_broadcast_action, trackId);
releaseWakeLock();
showNotification();
sendTrackBroadcast(R.string.track_stopped_broadcast_action, trackId);
stopSelf();
}
/**
@@ -730,17 +778,6 @@ public class TrackRecordingService extends Service {
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
}
/**
* Updates a {@link TripStatistics} to a time.
*
* @param tripStatistics the trip statistics
* @param time the time
*/
private void updateTripStatisticsToTime(TripStatistics tripStatistics, long time) {
tripStatistics.setStopTime(time);
tripStatistics.setTotalTime(time - tripStatistics.getStartTime());
}
/**
* Called when location changed.
*
@@ -748,13 +785,13 @@ public class TrackRecordingService extends Service {
*/
private void onLocationChangedAsync(Location location) {
try {
if (location == null) {
Log.w(TAG, "Ignore onLocationChangedAsync. location is null.");
if (!isRecording() || isPaused()) {
Log.w(TAG, "Ignore onLocationChangedAsync. Not recording or paused.");
return;
}
if (!isRecording()) {
Log.w(TAG, "Ignore onLocationChangedAsync. Not recording.");
if (!LocationUtils.isValidLocation(location)) {
Log.w(TAG, "Ignore onLocationChangedAsync. location is invalid.");
return;
}
@@ -763,74 +800,56 @@ public class TrackRecordingService extends Service {
return;
}
recordingTrack = myTracksProviderUtils.getTrack(recordingTrackId);
if (recordingTrack == null) {
Log.d(TAG, "Ignore onLocationChangedAsync. recodingTrack is null.");
return;
}
locationListenerPolicy.updateIdleTime(trackTripStatisticsBuilder.getIdleTime());
if (LocationUtils.isValidLocation(location)) {
long now = System.currentTimeMillis();
trackTripStatisticsBuilder.addLocation(location, now);
markerTripStatisticsBuilder.addLocation(location, now);
}
Location lastTrackLocation = myTracksProviderUtils.getLastTrackLocation(recordingTrackId);
SensorDataSet sensorDataSet = getSensorDataSet();
long idleTime = currentSegmentHasLocation && LocationUtils.isValidLocation(lastTrackLocation) ?
location.getTime() - lastTrackLocation.getTime()
: 0;
locationListenerPolicy.updateIdleTime(idleTime);
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
registerLocationListener();
}
Location lastRecordedLocation = myTracksProviderUtils.getLastLocation();
double distanceToLastRecordedLocation = lastRecordedLocation != null ? location.distanceTo(
lastRecordedLocation)
: Double.POSITIVE_INFINITY;
double distanceToLastLocation = lastLocation != null ? location.distanceTo(lastLocation)
: Double.POSITIVE_INFINITY;
boolean hasSensorData = sensorManager != null && sensorManager.isEnabled()
&& sensorManager.getSensorDataSet() != null && sensorManager.isSensorDataSetValid();
// If stationary for two locations, make sure the first one is recorded
if (distanceToLastLocation == 0 && !hasSensorData) {
if (isMoving) {
isMoving = false;
if (lastLocation != null && !lastLocation.equals(lastRecordedLocation)) {
if (!insertLocation(lastLocation, lastRecordedLocation, recordingTrackId)) {
return;
}
}
}
// Always insert the first segment location
if (!currentSegmentHasLocation) {
insertLocation(location, sensorDataSet, lastTrackLocation);
currentSegmentHasLocation = true;
lastLocation = location;
} else if (distanceToLastRecordedLocation >= minRecordingDistance || hasSensorData) {
if (!isMoving) {
isMoving = true;
// Make sure the lastLocation is added.
if (lastLocation != null && !lastLocation.equals(lastRecordedLocation)) {
if (!insertLocation(lastLocation, lastRecordedLocation, recordingTrackId)) {
return;
}
}
}
boolean startNewSegment = LocationUtils.isValidLocation(lastRecordedLocation)
&& distanceToLastRecordedLocation > maxRecordingDistance
&& recordingTrack.getStartId() >= 0;
if (startNewSegment) {
Log.d(TAG, "Inserting a separator track point.");
Location separator = new Location(LocationManager.GPS_PROVIDER);
separator.setLongitude(0);
separator.setLatitude(100);
separator.setTime(lastRecordedLocation.getTime());
myTracksProviderUtils.insertTrackPoint(separator, recordingTrackId);
}
if (!insertLocation(location, lastRecordedLocation, recordingTrackId)) {
return;
}
lastLocation = location;
} else {
Log.d(TAG, "Ignore location. distanceToLastRecordedLocation < minRecordingDistance.");
return;
}
if (!LocationUtils.isValidLocation(lastTrackLocation)) {
/*
* Should not happen. The current segment should have a location. Just
* insert the current location.
*/
insertLocation(location, sensorDataSet, lastTrackLocation);
lastLocation = location;
return;
}
double distanceToLastTrackLocation = location.distanceTo(lastTrackLocation);
if (distanceToLastTrackLocation < minRecordingDistance && sensorDataSet == null) {
// skip
} else if (distanceToLastTrackLocation > maxRecordingDistance) {
insertLocation(lastLocation, sensorDataSet, lastTrackLocation);
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
pause.setLatitude(PAUSE_LATITUDE);
pause.setTime(lastLocation.getTime());
insertLocation(pause, null, null);
insertLocation(location, sensorDataSet, lastTrackLocation);
} else {
/*
* (distanceToLastTrackLocation >= minRecordingDistance ||
* hasSensorData) && distanceToLastTrackLocation <= maxRecordingDistance
*/
insertLocation(lastLocation, sensorDataSet, lastTrackLocation);
insertLocation(location, sensorDataSet, lastTrackLocation);
}
lastLocation = location;
} catch (Error e) {
Log.e(TAG, "Error in onLocationChangedAsync", e);
throw e;
@@ -840,62 +859,28 @@ public class TrackRecordingService extends Service {
}
}
/**
* Inserts a new location in the track points db and updates the corresponding
* track in the track db.
*
* @param location the location to be inserted
* @param lastRecordedLocation the last recorded location before this one (or
* null if none)
* @param trackId the id of the track
* @return true if successful. False if SQLite3 threw an exception.
*/
private boolean insertLocation(Location location, Location lastRecordedLocation, long trackId) {
// Keep track of length along recorded track
if (LocationUtils.isValidLocation(location)) {
if (lastLengthLocation != null) {
length += location.distanceTo(lastLengthLocation);
}
lastLengthLocation = location;
private void insertLocation(
Location location, SensorDataSet sensorDataSet, Location lastRecordedLocation) {
Track track = myTracksProviderUtils.getTrack(recordingTrackId);
if (track == null) {
return;
}
if (location == null) {
return;
}
if (location.equals(lastRecordedLocation)) {
return;
}
try {
Location locationToInsert = location;
if (sensorManager != null && sensorManager.isEnabled()) {
SensorDataSet sensorDataSet = sensorManager.getSensorDataSet();
if (sensorDataSet != null && sensorManager.isSensorDataSetValid()) {
locationToInsert = new MyTracksLocation(location, sensorDataSet);
}
}
Uri uri = myTracksProviderUtils.insertTrackPoint(locationToInsert, trackId);
int pointId = Integer.parseInt(uri.getLastPathSegment());
// Update the current track
if (lastRecordedLocation != null && lastRecordedLocation.getLatitude() <= 90) {
if (recordingTrack.getStartId() < 0) {
recordingTrack.setStartId(pointId);
}
recordingTrack.setStopId(pointId);
recordingTrack.setNumberOfPoints(recordingTrack.getNumberOfPoints() + 1);
long now = System.currentTimeMillis();
TripStatistics tripStatistics = trackTripStatisticsBuilder.getTripStatistics();
updateTripStatisticsToTime(tripStatistics, now);
recordingTrack.setTripStatistics(tripStatistics);
myTracksProviderUtils.updateTrack(recordingTrack);
// Update the first waypoint
Waypoint waypoint = myTracksProviderUtils.getFirstWaypoint(recordingTrackId);
if (waypoint != null) {
TripStatistics stats = trackTripStatisticsBuilder.getTripStatistics();
waypoint.setLength(length);
waypoint.setDuration(now - stats.getStartTime());
waypoint.setTripStatistics(stats);
myTracksProviderUtils.updateWaypoint(waypoint);
}
if (sensorDataSet != null) {
location = new MyTracksLocation(location, sensorDataSet);
}
Uri uri = myTracksProviderUtils.insertTrackPoint(location, recordingTrackId);
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsBuilder.addLocation(location, minRecordingDistance);
markerTripStatisticsBuilder.addLocation(location, minRecordingDistance);
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5
@@ -903,17 +888,42 @@ public class TrackRecordingService extends Service {
* listener gets invoked twice at about the same time).
*/
Log.w(TAG, "SQLiteException", e);
return false;
}
announcementExecutor.update();
splitExecutor.update();
return true;
}
private void updateRecordingTrack(
Track track, long trackPointId, boolean isTrackPointNewAndValid) {
if (trackPointId >= 0) {
if (track.getStartId() < 0) {
track.setStartId(trackPointId);
}
track.setStopId(trackPointId);
}
if (isTrackPointNewAndValid) {
track.setNumberOfPoints(track.getNumberOfPoints() + 1);
}
trackTripStatisticsBuilder.updateTime(System.currentTimeMillis());
track.setTripStatistics(trackTripStatisticsBuilder.getTripStatistics());
myTracksProviderUtils.updateTrack(track);
}
private SensorDataSet getSensorDataSet() {
if (sensorManager == null || !sensorManager.isEnabled()
|| !sensorManager.isSensorDataSetValid()) {
return null;
}
return sensorManager.getSensorDataSet();
}
/**
* Registers the location listener.
*/
private void registerLocationListener() {
unregisterLocationListener();
if (myTracksLocationManager == null) {
Log.e(TAG, "locationManager is null.");
return;
@@ -981,7 +991,7 @@ public class TrackRecordingService extends Service {
* Shows the notification.
*/
private void showNotification() {
if (isRecording()) {
if (isRecording() && !isPaused()) {
Intent intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
TaskStackBuilder taskStackBuilder = TaskStackBuilder.from(this);
@@ -1070,7 +1080,7 @@ public class TrackRecordingService extends Service {
}
return trackRecordingService.isPaused();
}
@Override
public long getRecordingTrackId() {
if (!canAccess()) {
@@ -1102,7 +1112,7 @@ public class TrackRecordingService extends Service {
}
trackRecordingService.pauseCurrentTrack();
}
@Override
public void resumeCurrentTrack() {
if (!canAccess()) {
@@ -62,8 +62,8 @@ public class PeriodicTaskExecutor {
* Restores the executor.
*/
public void restore() {
if (!trackRecordingService.isRecording()) {
Log.d(TAG, "Not recording.");
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) {
Log.d(TAG, "Not recording or paused.");
return;
}
@@ -159,7 +159,7 @@ public class PeriodicTaskExecutor {
* Calculates the next distance for the distance periodic task.
*/
private void calculateNextTaskDistance() {
if (!trackRecordingService.isRecording() || periodicTask == null) {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused() || periodicTask == null) {
return;
}
@@ -50,7 +50,7 @@ public class TimerTaskExecutor {
* @param interval the interval in milliseconds
*/
public void scheduleTask(long interval) {
if (!trackRecordingService.isRecording()) {
if (!trackRecordingService.isRecording() || trackRecordingService.isPaused()) {
return;
}
@@ -19,132 +19,125 @@ package com.google.android.apps.mytracks.stats;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.util.Log;
/**
* Builder for {@link TripStatistics}. For keeping statistics as a track is
* paused/resumed and new locations are added.
* Builder for {@link TripStatistics}. For keeping track statistics as new
* locations are added. Note that some of the locations represent pause/resume
* separator.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class TripStatisticsBuilder {
// The trip statistics.
// The track's trip statistics
private final TripStatistics tripStatistics;
// The minimum recording distance.
private int minRecordingDistance = PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT;
// The current segment's trip statistics
private TripStatistics currentSegment;
// True if the trip is paused. All trips start as paused.
private boolean paused = true;
// The last location as reported by GPS.
// Current segment's last location.
private Location lastLocation;
// The last moving location that contributed to the moving statistics.
// Current segment's last moving location
private Location lastMovingLocation;
// A buffer of the recent speed readings (m/s) for calculating max speed.
// A buffer of the recent speed readings (m/s) for calculating max speed
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
// A buffer of the recent elevation readings (m).
// A buffer of the recent elevation readings (m)
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
Constants.ELEVATION_SMOOTHING_FACTOR);
// A buffer of the recent distance readings for calculating grade.
// A buffer of the recent distance readings (m) for calculating grade
private final DoubleBuffer distanceBuffer = new DoubleBuffer(Constants.DISTANCE_SMOOTHING_FACTOR);
// A buffer of the recent grade calculations
// A buffer of the recent grade calculations (%)
private final DoubleBuffer gradeBuffer = new DoubleBuffer(Constants.GRADE_SMOOTHING_FACTOR);
/**
* Creates a new trip starting at a start time.
* Creates a new trip statistics builder.
*
* @param startTime the start time
*/
public TripStatisticsBuilder(long startTime) {
tripStatistics = new TripStatistics();
resumeAt(startTime);
tripStatistics = init(startTime);
currentSegment = init(startTime);
}
public void updateTime(long time) {
currentSegment.setStopTime(time);
currentSegment.setTotalTime(time - currentSegment.getStartTime());
}
/**
* Creates a new trip, starting with an existing {@link TripStatistics}.
*
* @param other the existing {@link TripStatistics}
*/
public TripStatisticsBuilder(TripStatistics other) {
tripStatistics = new TripStatistics(other);
if (tripStatistics.getStartTime() > 0) {
resumeAt(tripStatistics.getStartTime());
}
}
/**
* Sets the min recording distance.
*
* @param minRecordingDistance the min recording distance
*/
public void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
}
/**
* Resumes the current track at a given time.
*
* @param time the time
*/
public void resumeAt(long time) {
if (!paused) {
return;
}
tripStatistics.setStartTime(time);
tripStatistics.setStopTime(-1L);
paused = false;
lastLocation = null;
lastMovingLocation = null;
speedBuffer.reset();
elevationBuffer.reset();
distanceBuffer.reset();
gradeBuffer.reset();
}
/**
* Pauses the track at a given time.
*
* @param time the time to pause at
*/
public void pauseAt(long time) {
if (paused) {
return;
}
tripStatistics.setStopTime(time);
// TODO: total time needs to take into account pauses
tripStatistics.setTotalTime(time - tripStatistics.getStartTime());
paused = true;
}
/**
* Gets the trip statistics.
* Gets the track's trip statistics.
*/
public TripStatistics getTripStatistics() {
// Take a snapshot - we don't want anyone messing with our internals
return new TripStatistics(tripStatistics);
// Take a snapshot - we don't want anyone messing with our tripStatistics
TripStatistics stats = new TripStatistics(tripStatistics);
stats.merge(currentSegment);
return stats;
}
/**
* Returns the amount of time the user has been idle or 0 if he is moving.
* Adds a location. TODO: This assume location has a valid time.
*
* @param location the location
* @param minRecordingDistance the min recording distance
*/
public long getIdleTime() {
if (lastLocation == null || lastMovingLocation == null) {
return 0;
public void addLocation(Location location, int minRecordingDistance) {
if (!LocationUtils.isValidLocation(location)) {
updateTime(location.getTime());
if (location.getLatitude() == TrackRecordingService.PAUSE_LATITUDE) {
tripStatistics.merge(currentSegment);
}
currentSegment = init(location.getTime());
lastLocation = null;
lastMovingLocation = null;
speedBuffer.reset();
elevationBuffer.reset();
distanceBuffer.reset();
gradeBuffer.reset();
return;
}
return lastLocation.getTime() - lastMovingLocation.getTime();
double elevationDifference = updateElevation(location.getAltitude());
currentSegment.updateLatitudeExtremities(location.getLatitude());
currentSegment.updateLongitudeExtremities(location.getLongitude());
if (lastLocation == null || lastMovingLocation == null) {
updateTime(location.getTime());
lastLocation = location;
lastMovingLocation = location;
return;
}
double movingDistance = lastMovingLocation.distanceTo(location);
if (movingDistance < minRecordingDistance
&& location.getSpeed() < Constants.MAX_NO_MOVEMENT_SPEED) {
updateTime(location.getTime());
lastLocation = location;
return;
}
long movingTime = location.getTime() - lastLocation.getTime();
if (movingTime < 0) {
updateTime(location.getTime());
lastLocation = location;
return;
}
currentSegment.addTotalDistance(movingDistance);
currentSegment.addMovingTime(movingTime);
updateSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
updateGrade(lastLocation.distanceTo(location), elevationDifference);
updateTime(location.getTime());
lastLocation = location;
lastMovingLocation = location;
}
/**
@@ -152,62 +145,11 @@ public class TripStatisticsBuilder {
* is noisy so the smoothed elevation is better than the raw elevation for
* many tasks.
*/
public double getSmoothedElevation() {
@VisibleForTesting
double getSmoothedElevation() {
return elevationBuffer.getAverage();
}
/**
* Adds a location. This will update all of the internal variables with this
* new location.
*
* @param location the location
* @param systemTime the system time for calculating totalTime. This should be
* the phone's system time (not GPS time)
* @return true if the person is moving
*/
public boolean addLocation(Location location, long systemTime) {
if (paused) {
Log.w(TAG, "Track is paused. Ignore addLocation.");
return false;
}
tripStatistics.setTotalTime(systemTime - tripStatistics.getStartTime());
double elevationDifference = updateElevation(location.getAltitude());
tripStatistics.updateLatitudeExtremities(location.getLatitude());
tripStatistics.updateLongitudeExtremities(location.getLongitude());
// If this is the first location, remember it and return.
if (lastLocation == null || lastMovingLocation == null) {
lastLocation = location;
lastMovingLocation = location;
return false;
}
// Don't do anything more if we didn't move since the last location.
double distance = lastLocation.distanceTo(location);
if (distance < minRecordingDistance && location.getSpeed() < Constants.MAX_NO_MOVEMENT_SPEED) {
lastLocation = location;
return false;
}
long timeDifference = location.getTime() - lastLocation.getTime();
if (timeDifference < 0) {
Log.e(TAG, "Negative time difference: " + timeDifference);
lastLocation = location;
return false;
}
tripStatistics.addTotalDistance(lastMovingLocation.distanceTo(location));
tripStatistics.addMovingTime(timeDifference);
updateSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
updateGrade(distance, elevationDifference);
lastLocation = location;
lastMovingLocation = location;
return true;
}
/**
* Updates a speed reading. Assumes the user is moving.
*
@@ -223,12 +165,12 @@ public class TripStatisticsBuilder {
return;
}
speedBuffer.setNext(speed);
if (speed > tripStatistics.getMaxSpeed()) {
tripStatistics.setMaxSpeed(speed);
if (speed > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(speed);
}
double movingSpeed = tripStatistics.getAverageMovingSpeed();
if (speedBuffer.isFull() && movingSpeed > tripStatistics.getMaxSpeed()) {
tripStatistics.setMaxSpeed(movingSpeed);
double movingSpeed = currentSegment.getAverageMovingSpeed();
if (speedBuffer.isFull() && movingSpeed > currentSegment.getMaxSpeed()) {
currentSegment.setMaxSpeed(movingSpeed);
}
}
@@ -242,10 +184,10 @@ public class TripStatisticsBuilder {
double oldAverage = elevationBuffer.getAverage();
elevationBuffer.setNext(elevation);
double newAverage = elevationBuffer.getAverage();
tripStatistics.updateElevationExtremities(newAverage);
currentSegment.updateElevationExtremities(newAverage);
double elevationDifference = elevationBuffer.isFull() ? newAverage - oldAverage : 0.0;
if (elevationDifference > 0) {
tripStatistics.addTotalElevationGain(elevationDifference);
currentSegment.addTotalElevationGain(elevationDifference);
}
return elevationDifference;
}
@@ -270,7 +212,14 @@ public class TripStatisticsBuilder {
return;
}
gradeBuffer.setNext(elevationDifference / smoothedDistance);
tripStatistics.updateGradeExtremities(gradeBuffer.getAverage());
currentSegment.updateGradeExtremities(gradeBuffer.getAverage());
}
private TripStatistics init(long time) {
TripStatistics stats = new TripStatistics();
stats.setStartTime(time);
stats.setStopTime(time);
return stats;
}
/**
+2
View File
@@ -23,6 +23,8 @@ limitations under the License.
<!-- Broadcast notification Intent constants -->
<string name="track_started_broadcast_action">com.google.android.apps.mytracks.TRACK_STARTED</string>
<string name="track_paused_broadcast_action">com.google.android.apps.mytracks.TRACK_PAUSED</string>
<string name="track_resumed_broadcast_action">com.google.android.apps.mytracks.TRACK_RESUMED</string>
<string name="track_stopped_broadcast_action">com.google.android.apps.mytracks.TRACK_STOPPED</string>
<!-- Next API refactor depricate track_id_broadcast_extra and rename it to remove the broadcast as it is used when SHARING tracks as -->
<string name="track_id_broadcast_extra">com.google.android.apps.mytracks.TRACK_ID</string>
@@ -101,16 +101,24 @@ public class TripStatistics implements Parcelable {
totalDistance += other.totalDistance;
totalTime += other.totalTime;
movingTime += other.movingTime;
latitudeExtremities.update(other.latitudeExtremities.getMin());
latitudeExtremities.update(other.latitudeExtremities.getMax());
longitudeExtremities.update(other.longitudeExtremities.getMin());
longitudeExtremities.update(other.longitudeExtremities.getMax());
if (other.latitudeExtremities.hasData()) {
latitudeExtremities.update(other.latitudeExtremities.getMin());
latitudeExtremities.update(other.latitudeExtremities.getMax());
}
if (other.longitudeExtremities.hasData()) {
longitudeExtremities.update(other.longitudeExtremities.getMin());
longitudeExtremities.update(other.longitudeExtremities.getMax());
}
maxSpeed = Math.max(maxSpeed, other.maxSpeed);
elevationExtremities.update(other.elevationExtremities.getMin());
elevationExtremities.update(other.elevationExtremities.getMax());
if (other.elevationExtremities.hasData()) {
elevationExtremities.update(other.elevationExtremities.getMin());
elevationExtremities.update(other.elevationExtremities.getMax());
}
totalElevationGain += other.totalElevationGain;
gradeExtremities.update(other.gradeExtremities.getMin());
gradeExtremities.update(other.gradeExtremities.getMax());
if (other.gradeExtremities.hasData()) {
gradeExtremities.update(other.gradeExtremities.getMin());
gradeExtremities.update(other.gradeExtremities.getMax());
}
}
/**
@@ -46,6 +46,7 @@ import android.test.RenamingDelegatingContext;
import android.test.ServiceTestCase;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.mock.MockCursor;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -143,6 +144,7 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
MyTracksProvider myTracksProvider = new MyTracksProvider();
myTracksProvider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, myTracksProvider);
MockContentProvider settingsProvider = new MockContentProvider(context) {
@Override
public Bundle call(String method, String arg, Bundle extras) {
@@ -155,7 +157,36 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
}
};
mockContentResolver.addProvider(Settings.AUTHORITY, settingsProvider);
MockContentProvider googleSettingsProvider = new MockContentProvider(context) {
@Override
public Bundle call(String method, String arg, Bundle extras) {
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
MockCursor mockCursor = new MockCursor() {
@Override
public int getCount() {
return 1;
}
@Override
public boolean moveToNext() {
return true;
}
@Override
public String getString(int columnIndex) {
return MyTracksLocationManager.USE_LOCATION_FOR_SERVICES_ON;
}
@Override
public void close() {}
};
return mockCursor;
}
};
mockContentResolver.addProvider("com.google.settings", googleSettingsProvider);
// Set the context
setContext(context);
@@ -483,12 +514,8 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
try {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
fail("Expecting IllegalStateException");
} catch (IllegalStateException e) {
// Expected.
}
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
assertEquals(-1L, waypointId);
}
@MediumTest
@@ -497,6 +524,7 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
assertFalse(service.isPaused());
insertLocation(service);
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
@@ -520,12 +548,8 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
try {
service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
fail("Expecting IllegalStateException");
} catch (IllegalStateException e) {
// Expected.
}
long waypointId = service.insertWaypoint(WaypointCreationRequest.DEFAULT_WAYPOINT);
assertEquals(-1L, waypointId);
}
@MediumTest
@@ -700,6 +724,7 @@ public class TrackRecordingServiceTest extends ServiceTestCase<TestRecordingServ
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId()
: PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
private void fullRecordingSession() throws Exception {
@@ -3,31 +3,35 @@
package com.google.android.apps.mytracks.stats;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import android.location.Location;
import junit.framework.TestCase;
/**
* Test the the function of the TripStatisticsBuilder class.
* Tests {@link TripStatisticsBuilder}.
*
* @author Sandor Dornbush
*/
public class TripStatisticsBuilderTest extends TestCase {
private TripStatisticsBuilder builder = null;
private static final long ONE_SECOND = 1000;
private static final long TEN_SECONDS = 10 * ONE_SECOND;
private TripStatisticsBuilder tripStatisticsBuilder = null;
@Override
protected void setUp() throws Exception {
super.setUp();
builder = new TripStatisticsBuilder(System.currentTimeMillis());
tripStatisticsBuilder = new TripStatisticsBuilder(System.currentTimeMillis());
}
public void testAddLocationSimple() throws Exception {
builder = new TripStatisticsBuilder(1000);
TripStatistics tripStatistics = builder.getTripStatistics();
long startTime = 1000;
tripStatisticsBuilder = new TripStatisticsBuilder(startTime);
TripStatistics tripStatistics = tripStatisticsBuilder.getTripStatistics();
assertEquals(0.0, builder.getSmoothedElevation());
assertEquals(0.0, tripStatisticsBuilder.getSmoothedElevation());
assertEquals(Double.POSITIVE_INFINITY, tripStatistics.getMinElevation());
assertEquals(Double.NEGATIVE_INFINITY, tripStatistics.getMaxElevation());
assertEquals(0.0, tripStatistics.getMaxSpeed());
@@ -36,6 +40,8 @@ public class TripStatisticsBuilderTest extends TestCase {
assertEquals(0.0, tripStatistics.getTotalElevationGain());
assertEquals(0, tripStatistics.getMovingTime());
assertEquals(0.0, tripStatistics.getTotalDistance());
float speed = 11.1f;
for (int i = 0; i < 100; i++) {
Location location = new Location("test");
location.setAccuracy(1.0f);
@@ -43,126 +49,172 @@ public class TripStatisticsBuilderTest extends TestCase {
// Going up by 1 meter each time.
location.setAltitude(i);
location.setLatitude(i * .001); // Moving by .001 degree latitude (111 meters)
location.setSpeed(11.1f);
// Each time slice is 10 seconds.
long time = 1000 + 10000 * i;
location.setTime(time);
boolean moving = builder.addLocation(location, time);
assertEquals((i != 0), moving);
tripStatistics = builder.getTripStatistics();
assertEquals(10000 * i, tripStatistics.getTotalTime());
assertEquals(10000 * i, tripStatistics.getMovingTime());
assertEquals(i, builder.getSmoothedElevation(), Constants.ELEVATION_SMOOTHING_FACTOR / 2);
// Moving by .001 degree latitude (111 meters)
location.setLatitude(i * .001);
location.setSpeed(speed);
// Each time slice is 10 seconds.
location.setTime(startTime + i * TEN_SECONDS);
tripStatisticsBuilder.addLocation(location, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
tripStatistics = tripStatisticsBuilder.getTripStatistics();
assertEquals(i * TEN_SECONDS, tripStatistics.getTotalTime());
assertEquals(i * TEN_SECONDS, tripStatistics.getMovingTime());
assertEquals(i, tripStatisticsBuilder.getSmoothedElevation(),
Constants.ELEVATION_SMOOTHING_FACTOR / 2);
assertEquals(0.0, tripStatistics.getMinElevation());
assertEquals(i, tripStatistics.getMaxElevation(), Constants.ELEVATION_SMOOTHING_FACTOR / 2);
assertEquals(i, tripStatistics.getTotalElevationGain(), Constants.ELEVATION_SMOOTHING_FACTOR);
if (i > Constants.SPEED_SMOOTHING_FACTOR) {
assertEquals(11.1f, tripStatistics.getMaxSpeed(), 0.1);
if (i >= Constants.SPEED_SMOOTHING_FACTOR) {
assertEquals(speed, tripStatistics.getMaxSpeed(), 0.1);
}
if (i > Constants.DISTANCE_SMOOTHING_FACTOR && i > Constants.ELEVATION_SMOOTHING_FACTOR) {
if (i >= Constants.DISTANCE_SMOOTHING_FACTOR && i >= Constants.ELEVATION_SMOOTHING_FACTOR) {
// 1 m / 111 m = .009
assertEquals(0.009, tripStatistics.getMinGrade(), 0.0001);
assertEquals(0.009, tripStatistics.getMaxGrade(), 0.0001);
}
assertEquals(111.0 * i, tripStatistics.getTotalDistance(), 100);
assertEquals(i * 111.0, tripStatistics.getTotalDistance(), 100);
}
}
/**
* Test that elevation works if the user is stable.
* Tests {@link TripStatisticsBuilder#updateElevation(double)} with constant
* elevations.
*/
public void testElevationSimple() throws Exception {
for (double elevation = 0; elevation < 1000; elevation += 10) {
builder = new TripStatisticsBuilder(System.currentTimeMillis());
for (int j = 0; j < 100; j++) {
assertEquals(0.0, builder.updateElevation(elevation));
assertEquals(elevation, builder.getSmoothedElevation());
TripStatistics data = builder.getTripStatistics();
assertEquals(elevation, data.getMinElevation());
assertEquals(elevation, data.getMaxElevation());
assertEquals(0.0, data.getTotalElevationGain());
tripStatisticsBuilder = new TripStatisticsBuilder(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
assertEquals(0.0, tripStatisticsBuilder.updateElevation(elevation));
assertEquals(elevation, tripStatisticsBuilder.getSmoothedElevation());
TripStatistics tripStatistics = tripStatisticsBuilder.getTripStatistics();
assertEquals(elevation, tripStatistics.getMinElevation());
assertEquals(elevation, tripStatistics.getMaxElevation());
assertEquals(0.0, tripStatistics.getTotalElevationGain());
}
}
}
/**
* Tests {@link TripStatisticsBuilder#updateGrade(double, double)} with
* elevation gain.
*/
public void testElevationGain() throws Exception {
for (double i = 0; i < 1000; i++) {
double expectedGain;
if (i < (Constants.ELEVATION_SMOOTHING_FACTOR - 1)) {
if (i < Constants.ELEVATION_SMOOTHING_FACTOR - 1) {
expectedGain = 0;
} else if (i < Constants.ELEVATION_SMOOTHING_FACTOR) {
expectedGain = 0.5;
} else {
expectedGain = 1.0;
}
assertEquals(expectedGain, builder.updateElevation(i));
assertEquals(i, builder.getSmoothedElevation(), 20);
TripStatistics data = builder.getTripStatistics();
assertEquals(0.0, data.getMinElevation(), 0.0);
assertEquals(i, data.getMaxElevation(), Constants.ELEVATION_SMOOTHING_FACTOR);
assertEquals(expectedGain, tripStatisticsBuilder.updateElevation(i));
assertEquals(i, tripStatisticsBuilder.getSmoothedElevation(),
Constants.ELEVATION_SMOOTHING_FACTOR / 2);
TripStatistics data = tripStatisticsBuilder.getTripStatistics();
assertEquals(0.0, data.getMinElevation());
assertEquals(i, data.getMaxElevation(), Constants.ELEVATION_SMOOTHING_FACTOR / 2);
assertEquals(i, data.getTotalElevationGain(), Constants.ELEVATION_SMOOTHING_FACTOR);
}
}
/**
* Tests {@link TripStatisticsBuilder#updateGrade(double, double)} with grade
* of 1 and -1.
*/
public void testGradeSimple() throws Exception {
for (double i = 0; i < 1000; i++) {
// The value of the elevation does not matter. This is just to fill the
// buffer.
builder.updateElevation(i);
builder.updateGrade(100, 100);
if ((i > Constants.GRADE_SMOOTHING_FACTOR) && (i > Constants.ELEVATION_SMOOTHING_FACTOR)) {
assertEquals(1.0, builder.getTripStatistics().getMaxGrade());
assertEquals(1.0, builder.getTripStatistics().getMinGrade());
/*
* The value of the elevation does not matter. This is just to fill the
* elevation buffer.
*/
tripStatisticsBuilder.updateElevation(i);
tripStatisticsBuilder.updateGrade(100, 100);
if (i >= Constants.GRADE_SMOOTHING_FACTOR && i >= Constants.ELEVATION_SMOOTHING_FACTOR) {
assertEquals(1.0, tripStatisticsBuilder.getTripStatistics().getMaxGrade());
assertEquals(1.0, tripStatisticsBuilder.getTripStatistics().getMinGrade());
}
}
for (double i = 0; i < 1000; i++) {
// The value of the elevation does not matter. This is just to fill the
// buffer.
builder.updateElevation(i);
builder.updateGrade(100, -100);
if ((i > Constants.GRADE_SMOOTHING_FACTOR) && (i > Constants.ELEVATION_SMOOTHING_FACTOR)) {
assertEquals(1.0, builder.getTripStatistics().getMaxGrade());
assertEquals(-1.0, builder.getTripStatistics().getMinGrade());
/*
* The value of the elevation does not matter. This is just to fill the
* elevation buffer.
*/
tripStatisticsBuilder.updateElevation(i);
tripStatisticsBuilder.updateGrade(100, -100);
if (i >= Constants.GRADE_SMOOTHING_FACTOR && i >= Constants.ELEVATION_SMOOTHING_FACTOR) {
assertEquals(1.0, tripStatisticsBuilder.getTripStatistics().getMaxGrade());
assertEquals(-1.0, tripStatisticsBuilder.getTripStatistics().getMinGrade());
}
}
}
/**
* Tests {@link TripStatisticsBuilder#updateGrade(double, double)} with
* distance of 1. The grade should get ignored.
*/
public void testGradeIgnoreShort() throws Exception {
for (double i = 0; i < 100; i++) {
// The value of the elevation does not matter. This is just to fill the
// buffer.
builder.updateElevation(i);
builder.updateGrade(1, 100);
assertEquals(Double.NEGATIVE_INFINITY, builder.getTripStatistics().getMaxGrade());
assertEquals(Double.POSITIVE_INFINITY, builder.getTripStatistics().getMinGrade());
/*
* The value of the elevation does not matter. This is just to fill the
* elevation buffer.
*/
tripStatisticsBuilder.updateElevation(i);
tripStatisticsBuilder.updateGrade(1, 100);
assertEquals(
Double.NEGATIVE_INFINITY, tripStatisticsBuilder.getTripStatistics().getMaxGrade());
assertEquals(
Double.POSITIVE_INFINITY, tripStatisticsBuilder.getTripStatistics().getMinGrade());
}
}
/**
* Tests {@link TripStatisticsBuilder#updateSpeed(long, double, long, double)}
* with speed of zero.
*/
public void testUpdateSpeedIncludeZero() {
for (int i = 0; i < 1000; i++) {
builder.updateSpeed(i + 1000, 0.0, i, 4.0);
assertEquals(0.0, builder.getTripStatistics().getMaxSpeed());
tripStatisticsBuilder.updateSpeed(i + ONE_SECOND, 0.0, i, 4.0);
assertEquals(0.0, tripStatisticsBuilder.getTripStatistics().getMaxSpeed());
}
}
/**
* Tests {@link TripStatisticsBuilder#updateSpeed(long, double, long, double)}
* with the error code 128. The speed should get ignored.
*/
public void testUpdateSpeedIngoreErrorCode() {
builder.updateSpeed(12345000, 128.0, 12344000, 0.0);
assertEquals(0.0, builder.getTripStatistics().getMaxSpeed());
long time = 12344000;
tripStatisticsBuilder.updateSpeed(time + ONE_SECOND, 128.0, time, 0.0);
assertEquals(0.0, tripStatisticsBuilder.getTripStatistics().getMaxSpeed());
}
/**
* Tests {@link TripStatisticsBuilder#updateSpeed(long, double, long, double)}
* with a large speed change. The speed should get ignored.
*/
public void testUpdateSpeedIngoreLargeAcceleration() {
builder.updateSpeed(12345000, 100.0, 12344000, 1.0);
assertEquals(0.0, builder.getTripStatistics().getMaxSpeed());
long time = 12344000;
tripStatisticsBuilder.updateSpeed(time + ONE_SECOND, 100.0, time, 1.0);
assertEquals(0.0, tripStatisticsBuilder.getTripStatistics().getMaxSpeed());
}
/**
* Tests {@link TripStatisticsBuilder#updateSpeed(long, double, long, double)}
* with constant speed.
*/
public void testUpdateSpeed() {
double speed = 4.0;
for (int i = 0; i < 1000; i++) {
builder.updateSpeed(i + 1000, 4.0, i, 4.0);
if (i > Constants.SPEED_SMOOTHING_FACTOR) {
assertEquals(4.0, builder.getTripStatistics().getMaxSpeed());
tripStatisticsBuilder.updateSpeed(i + ONE_SECOND, speed, i, speed);
if (i >= Constants.SPEED_SMOOTHING_FACTOR) {
assertEquals(speed, tripStatisticsBuilder.getTripStatistics().getMaxSpeed());
}
}
}