Merging Sandor's changes

This commit is contained in:
Rodrigo Damazio
2010-11-12 14:37:04 -08:00
5 changed files with 191 additions and 43 deletions
@@ -25,7 +25,7 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
/** /**
* This class will periodically announce the user's trip statitics. * This class will periodically announce the user's trip statistics.
* *
* @author Sandor Dornbush * @author Sandor Dornbush
*/ */
@@ -52,6 +52,11 @@ public class PeriodicTaskExecuter {
* @param interval The interval in milliseconds * @param interval The interval in milliseconds
*/ */
public void scheduleTask(long interval) { public void scheduleTask(long interval) {
// TODO: Decouple service from this class once and forever.
if (!service.isRecording()) {
return;
}
timer.cancel(); timer.cancel();
timer.purge(); timer.purge();
timer = new Timer(); timer = new Timer();
@@ -61,12 +66,14 @@ public class PeriodicTaskExecuter {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long next = service.getTripStatistics().getStartTime(); long next = service.getTripStatistics().getStartTime();
while (next < now) next += interval; if (next < now) {
next = now + interval - ((now - next) % interval);
}
Date start = new Date(next); Date start = new Date(next);
Log.i(MyTracksConstants.TAG, Log.i(MyTracksConstants.TAG,
"StatusAnnouncer scheduled to start at " + start + " every " task.getClass().getSimpleName() + " scheduled to start at " + start
+ interval + " milliseconds."); + " every " + interval + " milliseconds.");
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval); timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
} }
@@ -74,6 +81,8 @@ public class PeriodicTaskExecuter {
* Cleans up this object. * Cleans up this object.
*/ */
public void shutdown() { public void shutdown() {
Log.i(MyTracksConstants.TAG,
task.getClass().getSimpleName() + " shutting down.");
timer.cancel(); timer.cancel();
timer.purge(); timer.purge();
timer = null; timer = null;
@@ -73,6 +73,11 @@ public class SplitManager {
* Calculates the next distance that a split should be inserted at. * Calculates the next distance that a split should be inserted at.
*/ */
public void calculateNextSplit() { public void calculateNextSplit() {
// TODO: Decouple service from this class once and forever.
if (!service.isRecording()) {
return;
}
if (splitFrequency >= 0) { if (splitFrequency >= 0) {
nextSplitDistance = Double.MAX_VALUE; nextSplitDistance = Double.MAX_VALUE;
Log.d(MyTracksConstants.TAG, Log.d(MyTracksConstants.TAG,
@@ -121,6 +126,12 @@ public class SplitManager {
*/ */
public void setSplitFrequency(int splitFrequency) { public void setSplitFrequency(int splitFrequency) {
this.splitFrequency = splitFrequency; this.splitFrequency = splitFrequency;
// TODO: Decouple service from this class once and forever.
if (!service.isRecording()) {
return;
}
if (splitFrequency < 1) { if (splitFrequency < 1) {
if (splitExecuter != null) { if (splitExecuter != null) {
splitExecuter.shutdown(); splitExecuter.shutdown();
@@ -239,8 +239,10 @@ public class StatusAnnouncerTask implements PeriodicTask {
// Stop listening to phone state. // Stop listening to phone state.
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_NONE); listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_NONE);
tts.shutdown(); if (tts != null) {
tts = null; tts.shutdown();
tts = null;
}
} }
/** /**
@@ -663,9 +663,11 @@ public class TrackRecordingService extends Service implements LocationListener {
restoreStats(recordingTrack); restoreStats(recordingTrack);
isRecording = true; isRecording = true;
} else { } else {
// Make sure we have consistent state in shared preferences. if (recordingTrackId != -1) {
Log.w(MyTracksConstants.TAG, "TrackRecordingService.onCreate: Resetting " // Make sure we have consistent state in shared preferences.
+ "an orphaned recording track: " + recordingTrackId); Log.w(MyTracksConstants.TAG, "TrackRecordingService.onCreate: "
+ "Resetting an orphaned recording track = " + recordingTrackId);
}
prefManager.setRecordingTrack(recordingTrackId = -1); prefManager.setRecordingTrack(recordingTrackId = -1);
} }
showNotification(); showNotification();
@@ -677,7 +679,9 @@ public class TrackRecordingService extends Service implements LocationListener {
* the announcements, otherwise this method is no-op. * the announcements, otherwise this method is no-op.
*/ */
private void setUpAnnouncer() { private void setUpAnnouncer() {
if (announcementFrequency != -1) { Log.d(MyTracksConstants.TAG, "TrackRecordingService.setUpAnnouncer: "
+ announcementExecuter);
if (announcementFrequency != -1 && recordingTrackId != -1) {
if (announcementExecuter == null) { if (announcementExecuter == null) {
StatusAnnouncerFactory statusAnnouncerFactory = StatusAnnouncerFactory statusAnnouncerFactory =
new StatusAnnouncerFactory(ApiFeatures.getInstance()); new StatusAnnouncerFactory(ApiFeatures.getInstance());
@@ -689,6 +693,18 @@ public class TrackRecordingService extends Service implements LocationListener {
announcementExecuter.scheduleTask(announcementFrequency * 60000); announcementExecuter.scheduleTask(announcementFrequency * 60000);
} }
} }
private void shutdownAnnouncer() {
Log.d(MyTracksConstants.TAG, "TrackRecordingService.shutdownAnnouncer: "
+ announcementExecuter);
if (announcementExecuter != null) {
try {
announcementExecuter.shutdown();
} finally {
announcementExecuter = null;
}
}
}
@Override @Override
public void onDestroy() { public void onDestroy() {
@@ -701,9 +717,7 @@ public class TrackRecordingService extends Service implements LocationListener {
isRecording = false; isRecording = false;
showNotification(); showNotification();
unregisterLocationListener(); unregisterLocationListener();
if (announcementExecuter != null) { shutdownAnnouncer();
announcementExecuter.shutdown();
}
splitManager.shutdown(); splitManager.shutdown();
super.onDestroy(); super.onDestroy();
} }
@@ -936,6 +950,7 @@ public class TrackRecordingService extends Service implements LocationListener {
} }
isRecording = false; isRecording = false;
shutdownAnnouncer();
Track recordingTrack = providerUtils.getTrack(recordingTrackId); Track recordingTrack = providerUtils.getTrack(recordingTrackId);
if (recordingTrack != null) { if (recordingTrack != null) {
TripStatistics stats = recordingTrack.getStatistics(); TripStatistics stats = recordingTrack.getStatistics();
@@ -1042,10 +1057,7 @@ public class TrackRecordingService extends Service implements LocationListener {
public void setAnnouncementFrequency(int announcementFrequency) { public void setAnnouncementFrequency(int announcementFrequency) {
this.announcementFrequency = announcementFrequency; this.announcementFrequency = announcementFrequency;
if (announcementFrequency == -1) { if (announcementFrequency == -1) {
if (announcementExecuter != null) { shutdownAnnouncer();
announcementExecuter.shutdown();
announcementExecuter = null;
}
} else { } else {
setUpAnnouncer(); setUpAnnouncer();
} }
@@ -46,6 +46,11 @@ import java.util.List;
* Tests for the MyTracks track recording service. * Tests for the MyTracks track recording service.
* *
* @author Bartlomiej Niechwiej * @author Bartlomiej Niechwiej
*
* TODO: The original class, ServiceTestCase, has a few limitations, e.g.
* it's not possible to properly shutdown the service, unless tearDown()
* is called, which prevents from testing multiple scenarios in a single
* test (see runFunctionTest for more details).
*/ */
public class TrackRecordingServiceTest public class TrackRecordingServiceTest
extends ServiceTestCase<TrackRecordingService> { extends ServiceTestCase<TrackRecordingService> {
@@ -141,6 +146,9 @@ public class TrackRecordingServiceTest
sharedPreferences = context.getSharedPreferences( sharedPreferences = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0); MyTracksSettings.SETTINGS_NAME, 0);
// Let's use default values.
sharedPreferences.edit().clear().commit();
// Disable auto resume by default. // Disable auto resume by default.
updateAutoResumePrefs(0, -1); updateAutoResumePrefs(0, -1);
// No recording track. // No recording track.
@@ -376,32 +384,7 @@ public class TrackRecordingServiceTest
public void testIntegration_completeRecordingSession() throws Exception { public void testIntegration_completeRecordingSession() throws Exception {
List<Track> tracks = providerUtils.getAllTracks(); List<Track> tracks = providerUtils.getAllTracks();
assertTrue(tracks.isEmpty()); assertTrue(tracks.isEmpty());
fullRecordingSession();
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
assertTrue(id >= 0);
assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
assertEquals(id, sharedPreferences.getLong(
context.getString(R.string.recording_track_key), -1));
assertEquals(id, service.getRecordingTrackId());
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
assertFalse(service.isRecording());
assertEquals(-1, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
TripStatistics tripStatistics = track.getStatistics();
assertNotNull(tripStatistics);
assertTrue(tripStatistics.getStartTime() > 0);
assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
} }
@MediumTest @MediumTest
@@ -547,6 +530,80 @@ public class TrackRecordingServiceTest
assertEquals(1, service.insertWaypointMarker(waypoint)); assertEquals(1, service.insertWaypointMarker(waypoint));
} }
@MediumTest
public void testWithProperties_noAnnouncementFreq() throws Exception {
functionalTest(R.string.announcement_frequency_key, (Object) null);
}
@MediumTest
public void testWithProperties_defaultAnnouncementFreq() throws Exception {
functionalTest(R.string.announcement_frequency_key, 1);
}
@MediumTest
public void testWithProperties_noMaxRecordingDist() throws Exception {
functionalTest(R.string.max_recording_distance_key, (Object) null);
}
@MediumTest
public void testWithProperties_defaultMaxRecordingDist() throws Exception {
functionalTest(R.string.max_recording_distance_key, 5);
}
@MediumTest
public void testWithProperties_noMinRecordingDist() throws Exception {
functionalTest(R.string.min_recording_distance_key, (Object) null);
}
@MediumTest
public void testWithProperties_defaultMinRecordingDist() throws Exception {
functionalTest(R.string.min_recording_distance_key, 2);
}
@MediumTest
public void testWithProperties_noSignalSamplingFreq() throws Exception {
functionalTest(R.string.signal_sampling_frequency_key, (Object) null);
}
@MediumTest
public void testWithProperties_defaultSignalSamplingFreq() throws Exception {
functionalTest(R.string.signal_sampling_frequency_key, 1);
}
@MediumTest
public void testWithProperties_noSplitFreq() throws Exception {
functionalTest(R.string.split_frequency_key, (Object) null);
}
@MediumTest
public void testWithProperties_defaultSplitFreqByDist() throws Exception {
functionalTest(R.string.split_frequency_key, 5);
}
@MediumTest
public void testWithProperties_defaultSplitFreqByTime() throws Exception {
functionalTest(R.string.split_frequency_key, -2);
}
@MediumTest
public void testWithProperties_noMetricUnits() throws Exception {
functionalTest(R.string.metric_units_key, (Object) null);
}
@MediumTest
public void testWithProperties_metricUnitsEnabled() throws Exception {
functionalTest(R.string.metric_units_key, true);
}
@MediumTest
public void testWithProperties_metricUnitsDisabled() throws Exception {
functionalTest(R.string.metric_units_key, false);
}
// TODO: Add the following tests:
// R.string.min_recording_interval_key
// R.string.min_required_accuracy_key
private ITrackRecordingService bindAndGetService(Intent intent) { private ITrackRecordingService bindAndGetService(Intent intent) {
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface( ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(
bindService(intent)); bindService(intent));
@@ -592,4 +649,61 @@ public class TrackRecordingServiceTest
editor.putLong(context.getString(R.string.recording_track_key), id); editor.putLong(context.getString(R.string.recording_track_key), id);
editor.commit(); editor.commit();
} }
// TODO: We support multiple values for readability, however this test's
// base class doesn't properly shutdown the service, so it's not possible
// to pass more than 1 value at a time.
private void functionalTest(int resourceId, Object ...values)
throws Exception {
final String key = context.getString(resourceId);
for (Object value : values) {
// Remove all properties and set the property for the given key.
Editor editor = sharedPreferences.edit();
editor.clear();
if (value instanceof String) {
editor.putString(key, (String) value);
} else if (value instanceof Long) {
editor.putLong(key, (Long) value);
} else if (value instanceof Integer) {
editor.putInt(key, (Integer) value);
} else if (value instanceof Boolean) {
editor.putBoolean(key, (Boolean) value);
} else if (value == null) {
// Do nothing, as clear above has already removed this property.
}
editor.commit();
fullRecordingSession();
}
}
private void fullRecordingSession() throws Exception {
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
assertTrue(id >= 0);
assertTrue(service.isRecording());
Track track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
assertEquals(id, sharedPreferences.getLong(
context.getString(R.string.recording_track_key), -1));
assertEquals(id, service.getRecordingTrackId());
// TODO: Add a few locations, insert markers, etc.
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
assertFalse(service.isRecording());
assertEquals(-1, service.getRecordingTrackId());
track = providerUtils.getTrack(id);
assertNotNull(track);
assertEquals(id, track.getId());
TripStatistics tripStatistics = track.getStatistics();
assertNotNull(tripStatistics);
assertTrue(tripStatistics.getStartTime() > 0);
assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
}
} }