diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java index 5359717cc..418c78d95 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java @@ -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; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java index 2bfa6c1a2..8059b1057 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java index 31370e64a..27373ae72 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/fusiontables/SendFusionTablesAsyncTask.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java index e46720b8c..65048d74f 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/maps/SendMapsAsyncTask.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/MyTracksLocationManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/MyTracksLocationManager.java index 42a5d4b4e..b78a1b761 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/MyTracksLocationManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/MyTracksLocationManager.java @@ -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"; diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index 5ddd8884e..6570f8b3d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -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()) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java index 14a92c684..112ea4db2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecutor.java @@ -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; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java index 4d9364de7..8335116fc 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimerTaskExecutor.java @@ -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; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java index 18f7103b9..1220c66e3 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java +++ b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java @@ -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; } /** diff --git a/MyTracksLib/res/values/third_party.xml b/MyTracksLib/res/values/third_party.xml index ef628f352..a216c7a9a 100644 --- a/MyTracksLib/res/values/third_party.xml +++ b/MyTracksLib/res/values/third_party.xml @@ -23,6 +23,8 @@ limitations under the License. com.google.android.apps.mytracks.TRACK_STARTED + com.google.android.apps.mytracks.TRACK_PAUSED + com.google.android.apps.mytracks.TRACK_RESUMED com.google.android.apps.mytracks.TRACK_STOPPED com.google.android.apps.mytracks.TRACK_ID diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java b/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java index 892c0d590..cee446305 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/stats/TripStatistics.java @@ -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()); + } } /** diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java index d64cd82bd..93e11a48e 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java @@ -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 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()); } } }