Refactor periodic tasks.

This commit is contained in:
Sandor Dornbush
2011-04-05 21:34:47 -07:00
parent 9616084769
commit 8d3ab8f93b
24 changed files with 767 additions and 1773 deletions
@@ -19,29 +19,21 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -58,48 +50,28 @@ import java.util.ArrayList;
* An activity that displays a chart from the track point provider.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartActivity extends Activity implements
SharedPreferences.OnSharedPreferenceChangeListener {
private final static int BUFFER_SIZE = 1024;
private double profileLength = 0;
private boolean metricUnits = true;
private boolean reportSpeed = true;
/**
* The track id that is displayed.
*/
private long selectedTrackId = -1;
/**
* Id of the last location that was seen when reading tracks from the
* provider. This is used to determine which locations are new compared to the
* last time the chart was updated.
*/
private long lastSeenLocationId = -1;
private long startTime = -1;
private Location lastLocation;
/**
* The id of the track currently being recorded.
*/
private long recordingTrackId = -1;
public class ChartActivity extends Activity implements TrackDataListener {
private final DoubleBuffer elevationBuffer =
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer =
new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
private TrackDataHub dataHub;
// Stats gathered from received data.
private double profileLength = 0;
private long startTime = -1;
private Location lastLocation;
private double trackMaxSpeed;
// Modes of operation
private Mode mode = Mode.BY_DISTANCE;
/**
* Utilities to deal with the database.
*/
private MyTracksProviderUtils providerUtils;
private boolean metricUnits;
private boolean reportSpeed;
/*
* UI elements:
@@ -109,9 +81,6 @@ public class ChartActivity extends Activity implements
private LinearLayout busyPane;
private ZoomControls zoomControls;
/** Handler for callbacks to the UI thread */
private final Handler uiHandler = new Handler();
/**
* A runnable that can be posted to the UI thread. It will remove the spinner
* (if any), enable/disable zoom controls and orange pointer as appropriate
@@ -123,89 +92,17 @@ public class ChartActivity extends Activity implements
busyPane.setVisibility(View.GONE);
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(selectedTrackIsRecording());
chartView.setShowPointer(dataHub.isRecordingSelected());
chartView.invalidate();
}
};
/**
* A runnable that can be posted to the UI thread. It will show the spinner.
*/
private final Runnable showSpinner = new Runnable() {
@Override
public void run() {
busyPane.setVisibility(View.VISIBLE);
}
};
/**
* An observer for the tracks provider. Will listen to new track points being
* added and update the chart if necessary.
*/
private ContentObserver observer;
/**
* An observer for the waypoints provider. Will listen to new way points being
* added and update the chart if necessary.
*/
private ContentObserver waypointObserver;
/**
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
* this thread.
*/
private final HandlerThread updateTrackThread =
new HandlerThread("updateTrackThread");
/** Handler for updateTrackThread */
private Handler updateTrackHandler;
/**
* A runnable that updates the profile from the provider.
*/
private final Runnable updateTrackRunnable = new Runnable() {
@Override
public void run() {
readNewTrackPoints();
}
};
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key != null) {
if (key.equals(getString(R.string.selected_track_key))) {
selectedTrackId =
sharedPreferences.getLong(getString(R.string.selected_track_key),
-1);
readProfileAsync();
} else if (key.equals(getString(R.string.metric_units_key))) {
metricUnits =
sharedPreferences.getBoolean(getString(R.string.metric_units_key),
true);
chartView.setMetricUnits(metricUnits);
readProfileAsync();
} else if (key.equals(getString(R.string.report_speed_key))) {
reportSpeed =
sharedPreferences.getBoolean(getString(R.string.report_speed_key),
true);
chartView.setReportSpeed(reportSpeed, this);
readProfileAsync();
} else if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId =
sharedPreferences.getLong(getString(R.string.recording_track_key),
-1);
runOnUiThread(updateChart);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "ChartActivity.onCreate");
super.onCreate(savedInstanceState);
MyTracks.getInstance().setChartActivity(this);
providerUtils = MyTracksProviderUtils.Factory.get(this);
dataHub = MyTracks.getInstance().getDataHub();
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
@@ -221,22 +118,6 @@ public class ChartActivity extends Activity implements
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.addView(chartView, params);
SharedPreferences preferences =
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (preferences != null) {
selectedTrackId =
preferences.getLong(getString(R.string.selected_track_key), -1);
recordingTrackId =
preferences.getLong(getString(R.string.recording_track_key), -1);
metricUnits = preferences.getBoolean(getString(R.string.metric_units_key),
true);
chartView.setMetricUnits(metricUnits);
reportSpeed = preferences.getBoolean(getString(R.string.report_speed_key),
true);
chartView.setReportSpeed(reportSpeed, this);
preferences.registerOnSharedPreferenceChangeListener(this);
}
busyPane = (LinearLayout) findViewById(R.id.elevation_busypane);
zoomControls = (ZoomControls) findViewById(R.id.elevation_zoom);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@@ -251,97 +132,20 @@ public class ChartActivity extends Activity implements
zoomOut();
}
});
updateTrackThread.start();
updateTrackHandler = new Handler(updateTrackThread.getLooper());
// Register observer for the track point provider:
Handler contentHandler = new Handler();
observer = new ContentObserver(contentHandler) {
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "ChartActivity: ContentObserver.onChange");
// Check for any new locations and append them to the currently
// recording track.
if (recordingTrackId < 0) {
// No track is being recorded. We should not be here.
return;
}
if (selectedTrackId != recordingTrackId) {
// No track, or one other than the recording track is selected, don't
// bother.
return;
}
// Update can potentially be lengthy, put it in its own thread:
updateTrackHandler.post(updateTrackRunnable);
super.onChange(selfChange);
}
};
waypointObserver = new ContentObserver(contentHandler) {
@Override
public void onChange(boolean selfChange) {
Log.d(TAG,
"MyTracksMap: ContentObserver.onChange waypoints");
if (selectedTrackId < 0) {
return;
}
Thread t = new Thread() {
@Override
public void run() {
readWaypoints();
ChartActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.invalidate();
}
});
}
};
t.start();
super.onChange(selfChange);
}
};
readProfileAsync();
}
@Override
protected void onPause() {
super.onPause();
unregisterContentObservers();
protected void onStart() {
super.onStart();
dataHub.registerTrackDataListener(this);
}
@Override
protected void onResume() {
super.onResume();
// Make sure any updates that might have happened are propagated to this
// activity:
observer.onChange(false);
waypointObserver.onChange(false);
registerContentObservers();
}
protected void onStop() {
dataHub.unregisterTrackDataListener(this);
/**
* Register the content observer for the map overlay.
*/
private void registerContentObservers() {
getContentResolver().registerContentObserver(TrackPointsColumns.CONTENT_URI,
false/* notifyForDescendents */, observer);
getContentResolver().registerContentObserver(WaypointsColumns.CONTENT_URI,
false/* notifyForDescendents */, waypointObserver);
}
/**
* Unregister the content observer for the map overlay.
*/
private void unregisterContentObservers() {
getContentResolver().unregisterContentObserver(observer);
getContentResolver().unregisterContentObserver(waypointObserver);
}
private boolean selectedTrackIsRecording() {
return selectedTrackId == recordingTrackId;
super.onStop();
}
private void zoomIn() {
@@ -360,7 +164,7 @@ public class ChartActivity extends Activity implements
if (this.mode != newMode) {
this.mode = newMode;
chartView.setMode(this.mode);
readProfileAsync();
dataHub.reloadDataFor(this);
}
}
@@ -377,34 +181,6 @@ public class ChartActivity extends Activity implements
return chartView.getChartValueSeries(index).isEnabled();
}
private void readWaypoints() {
if (selectedTrackId < 0) {
return;
}
Cursor cursor = null;
chartView.clearWaypoints();
try {
// We will silently drop extra waypoints to make the app responsive.
cursor =
providerUtils.getWaypointsCursor(selectedTrackId, 0,
Constants.MAX_DISPLAYED_TRACK_POINTS);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
Waypoint wpt = providerUtils.createWaypoint(cursor);
chartView.addWaypoint(wpt);
} while (cursor.moveToNext());
}
}
} catch (RuntimeException e) {
Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -440,43 +216,41 @@ public class ChartActivity extends Activity implements
* This must be called in order for each point.
*
* @param location the location to get data for (this method takes ownership of that location)
* @param track the track to get data from
* @param result the resulting point to fill out
* @return the previous location, now available for reuse
*/
private Location getDataPoint(Location location, Track track, double[] result) {
private void fillDataPoint(Location location, double result[]) {
double timeOrDistance = Double.NaN,
elevation = Double.NaN,
speed = Double.NaN,
power = Double.NaN,
cadence = Double.NaN,
heartRate = Double.NaN;
if (location instanceof MyTracksLocation &&
((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet();
if (sensorData.hasPower()
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
&& sensorData.getPower().hasValue()) {
result[3] = sensorData.getPower().getValue();
} else {
result[3] = Double.NaN;
power = sensorData.getPower().getValue();
}
if (sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
result[4] = sensorData.getCadence().getValue();
} else {
result[4] = Double.NaN;
cadence = sensorData.getCadence().getValue();
}
if (sensorData.hasHeartRate()
&& sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorData.getHeartRate().hasValue()) {
result[5] = sensorData.getHeartRate().getValue();
} else {
result[5] = Double.NaN;
heartRate = sensorData.getHeartRate().getValue();
}
} else {
result[3] = Double.NaN;
result[4] = Double.NaN;
result[5] = Double.NaN;
}
// TODO: Account for segment splits?
switch (mode) {
case BY_DISTANCE:
result[0] = profileLength;
timeOrDistance = profileLength;
if (lastLocation != null) {
double d = lastLocation.distanceTo(location);
if (metricUnits) {
@@ -491,7 +265,7 @@ public class ChartActivity extends Activity implements
// Base case
startTime = location.getTime();
}
result[0] = (location.getTime() - startTime);
timeOrDistance = (location.getTime() - startTime);
break;
default:
Log.w(TAG, "ChartActivity unknown mode: " + mode);
@@ -500,7 +274,7 @@ public class ChartActivity extends Activity implements
elevationBuffer.setNext(metricUnits
? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
result[1] = elevationBuffer.getAverage();
elevation = elevationBuffer.getAverage();
if (lastLocation == null) {
if (Math.abs(location.getSpeed() - 128) > 1) {
@@ -509,151 +283,134 @@ public class ChartActivity extends Activity implements
} else if (TripStatisticsBuilder.isValidSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(),
lastLocation.getSpeed(), speedBuffer)
&& (location.getSpeed() <= track.getStatistics().getMaxSpeed())) {
&& (location.getSpeed() <= trackMaxSpeed)) {
speedBuffer.setNext(location.getSpeed());
}
result[2] = speedBuffer.getAverage() * 3.6;
speed = speedBuffer.getAverage() * 3.6;
if (!metricUnits) {
result[2] *= UnitConversions.KM_TO_MI;
speed *= UnitConversions.KM_TO_MI;
}
if (!reportSpeed && (result[2] != 0)) {
// Format as hours per unit
result[2] = (60.0 / result[2]);
if (!reportSpeed) {
if (speed != 0) {
// Format as hours per unit
speed = (60.0 / speed);
} else {
speed = Double.NaN;
}
}
Location oldLastLocation = lastLocation;
// Keep a copy so the location can be reused.
lastLocation = location;
if (oldLastLocation == null) {
// No previous location, but return a blank one for reuse
return new MyTracksLocation("");
if (result != null) {
result[0] = timeOrDistance;
result[1] = elevation;
result[2] = speed;
result[3] = power;
result[4] = cadence;
result[5] = heartRate;
}
return oldLastLocation;
}
/**
* Sets the chart data points reading from the provider. This is non-blocking.
*/
private void readProfileAsync() {
chartView.reset();
updateTrackHandler.post(new Runnable() {
@Override
public void onProviderStateChange(ProviderState state) {
// We don't care.
}
@Override
public void onCurrentLocationChanged(Location loc) {
// We don't care.
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
Log.e(TAG, "Visible", new Throwable());
runOnUiThread(new Runnable() {
@Override
public void run() {
runOnUiThread(showSpinner);
readProfile();
readWaypoints();
runOnUiThread(updateChart);
busyPane.setVisibility(View.VISIBLE);
}
});
}
/**
* Reads the track profile from the provider. This is a blocking function and
* should not be run from the UI thread.
*/
private void readProfile() {
@Override
public void onTrackUpdated(Track track) {
trackMaxSpeed = track.getStatistics().getMaxSpeed();
}
@Override
public void clearTrackPoints() {
profileLength = 0;
lastLocation = null;
startTime = -1;
if (selectedTrackId < 0) {
return;
}
Track track = providerUtils.getTrack(selectedTrackId);
if (track == null) {
return;
}
lastSeenLocationId = track.getStartId();
final ArrayList<double[]> theData = readPointsToList(track);
runOnUiThread(new Runnable() {
public void run() {
chartView.setDataPoints(theData);
}
});
elevationBuffer.reset();
speedBuffer.reset();
chartView.reset();
pendingPoints.clear();
}
/**
* Read all new track points.
*/
private void readNewTrackPoints() {
Log.i(TAG, "MyTracks: Updating chart last seen: " + lastSeenLocationId);
Track track = providerUtils.getTrack(recordingTrackId);
if (track == null) {
Log.w(TAG, "MyTracks: track not found");
return;
@Override
public void onNewTrackPoint(Location loc) {
if (LocationUtils.isValidLocation(loc)) {
double[] point = new double[6];
fillDataPoint(loc, point);
pendingPoints.add(point);
}
chartView.addDataPoints(readPointsToList(track));
uiHandler.post(new Runnable() {
public void run() {
chartView.invalidate();
}
});
Log.i(TAG, "MyTracks: Updated chart last seen: " + lastSeenLocationId);
}
/**
* Get the frequency at which points should be displayed.
* Limit the number of chart readings. Ideally we would want around 1024.
* @param track The track which will be displayed.
* @return The inverse of the frequency of points to be displayed.
*/
private int getSamplingFrequency(Track track) {
long totalLocations = track.getStopId() - track.getStartId();
return Math.max(1, (int) (totalLocations / 1024.0));
@Override
public void onSampledOutTrackPoint(Location loc) {
// Still account for the point in the smoothing buffers.
fillDataPoint(loc, null);
}
private Cursor getLocationsCursor(long lastLocationRead) {
return providerUtils.getLocationsCursor(selectedTrackId, lastLocationRead, BUFFER_SIZE, false);
@Override
public void onSegmentSplit() {
// Do nothing.
}
/**
* Read all of the points to a list.
* @param track The track which will be displayed.
* @return
*/
private ArrayList<double[]> readPointsToList(Track track) {
Cursor cursor = null;
long lastLocationRead = lastSeenLocationId;
int points = 0;
int chartSamplingFrequency = getSamplingFrequency(track);
ArrayList<double[]> result = new ArrayList<double[]>();
// Need two locations so we can keep track of the last location.
Location location = new MyTracksLocation("");
try {
while (lastSeenLocationId < track.getStopId()) {
cursor = getLocationsCursor(lastLocationRead);
if (cursor != null) {
elevationBuffer.reset();
speedBuffer.reset();
if (cursor.moveToFirst()) {
final int idColumnIdx = cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
while (cursor.moveToNext()) {
points++;
providerUtils.fillLocation(cursor, location);
if (MyTracksUtils.isValidLocation(location)) {
lastLocationRead = lastSeenLocationId =
cursor.getLong(idColumnIdx);
// TODO Can we be smarter about choosing 3 or 6 entries?
double[] point = new double[6];
location = getDataPoint(location, track, point);
if (points % chartSamplingFrequency == 0) {
result.add(point);
}
}
}
} else {
lastLocationRead += BUFFER_SIZE;
}
} else {
lastLocationRead += BUFFER_SIZE;
}
cursor.close();
cursor = null;
}
return result;
} finally {
if (cursor != null) {
cursor.close();
}
}
@Override
public void onNewTrackPointsDone() {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
runOnUiThread(updateChart);
}
@Override
public void clearWaypoints() {
chartView.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint wpt) {
chartView.addWaypoint(wpt);
}
@Override
public void onNewWaypointsDone() {
runOnUiThread(updateChart);
}
@Override
public boolean onUnitsChanged(boolean metric) {
this.metricUnits = metric;
chartView.setMetricUnits(metric);
return true; // Reload data
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
this.reportSpeed = reportSpeed;
chartView.setReportSpeed(reportSpeed, this);
return true; // Reload data
}
}
File diff suppressed because it is too large Load Diff
@@ -266,9 +266,6 @@ public class SettingsActivity extends PreferenceActivity {
final ListPreference minRequiredAccuracy =
(ListPreference) findPreference(
getString(R.string.min_required_accuracy_key));
final ListPreference splitFrequency =
(ListPreference) findPreference(
getString(R.string.split_frequency_key));
minRecordingDistance.setEntries(isMetric
? R.array.min_recording_distance_options
@@ -279,9 +276,16 @@ public class SettingsActivity extends PreferenceActivity {
minRequiredAccuracy.setEntries(isMetric
? R.array.min_required_accuracy_options
: R.array.min_required_accuracy_options_ft);
splitFrequency.setEntries(isMetric
? R.array.split_frequency_options
: R.array.split_frequency_options_ft);
setTaskOptions(isMetric, R.string.announcement_frequency_key);
setTaskOptions(isMetric, R.string.split_frequency_key);
}
private void setTaskOptions(boolean isMetric, int listId) {
final ListPreference taskFrequency =
(ListPreference) findPreference(getString(listId));
taskFrequency.setEntries(isMetric
? R.array.task_frequency_options
: R.array.task_frequency_options_ft);
}
@@ -17,26 +17,15 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.ContentObserver;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
@@ -44,66 +33,39 @@ import android.view.MenuItem;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
/**
* An activity that displays track statistics to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatsActivity extends Activity
implements OnSharedPreferenceChangeListener {
public class StatsActivity extends Activity implements TrackDataListener {
private StatsUtilities utils;
private UIUpdateThread thread;
private ContentObserver observer;
/**
* The id of the currently selected track.
*/
private long selectedTrackId = -1;
/**
* The id of the currently recording track.
*/
private long recordingTrackId = -1;
/**
* The start time of the selected track.
*/
private long startTime = -1;
/**
* True if distances should be displayed in metric units (from shared
* preferences).
*/
private boolean metricUnits = true;
/**
* True if pace should be displayed as dist/time (from shared preferences).
*/
private boolean displaySpeed = true;
/**
* true if activity has resumed and is on top
*/
private boolean activityOnTop = false;
/**
* If true, the statistics for the current segment are shown, otherwise
* for the full track.
*/
private boolean showCurrentSegment = false;
private MyTracksProviderUtils providerUtils;
private TrackDataHub dataHub;
/**
* A runnable for posting to the UI thread. Will update the total time field.
*/
private final Runnable updateResults = new Runnable() {
public void run() {
updateTotalTime();
if (dataHub.isRecordingSelected()) {
utils.setTime(R.id.total_time_register,
System.currentTimeMillis() - startTime);
}
}
};
@@ -121,13 +83,11 @@ public class StatsActivity extends Activity
public void run() {
Log.i(TAG, "Started UI update thread");
while (MyTracks.getInstance().isRecording()) {
long sleeptime = 1000;
runOnUiThread(updateResults);
try {
Thread.sleep(sleeptime);
Thread.sleep(1000L);
} catch (InterruptedException e) {
Log.w(TAG,
"StatsActivity: Caught exception on sleep.", e);
Log.w(TAG, "StatsActivity: Caught exception on sleep.", e);
break;
}
}
@@ -140,9 +100,9 @@ public class StatsActivity extends Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataHub = MyTracks.getInstance().getDataHub();
utils = new StatsUtilities(this);
providerUtils = new MyTracksProviderUtilsImpl(getContentResolver());
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
@@ -153,40 +113,10 @@ public class StatsActivity extends Activity
setContentView(R.layout.stats);
Handler contentHandler = new Handler();
observer = new ContentObserver(contentHandler) {
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "StatsActivity: ContentObserver.onChange");
restoreStats();
super.onChange(selfChange);
}
};
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
SharedPreferences preferences =
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (preferences != null) {
selectedTrackId = preferences.getLong(
getString(R.string.selected_track_key), -1);
recordingTrackId = preferences.getLong(
getString(R.string.recording_track_key), -1);
metricUnits = preferences.getBoolean(
getString(R.string.metric_units_key), true);
displaySpeed =
preferences.getBoolean(getString(R.string.report_speed_key), true);
checkLiveTrack();
restoreStats();
showUnknownLocation();
preferences.registerOnSharedPreferenceChangeListener(this);
}
utils.setMetricUnits(metricUnits);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
utils.setSpeedLabels();
showUnknownLocation();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
@@ -196,74 +126,49 @@ public class StatsActivity extends Activity
}
@Override
protected void onPause() {
unregisterLocationListener();
protected void onStart() {
dataHub.registerTrackDataListener(this);
super.onStart();
}
@Override
protected void onStop() {
dataHub.unregisterTrackDataListener(this);
if (thread != null) {
thread.interrupt();
thread = null;
}
getContentResolver().unregisterContentObserver(observer);
activityOnTop = false;
super.onPause();
super.onStop();
}
@Override
protected void onResume() {
activityOnTop = true;
checkLiveTrack();
restoreStats();
showUnknownLocation();
super.onResume();
public boolean onUnitsChanged(boolean metric) {
utils.setMetricUnits(metric);
updateLabels();
return true; // Reload data
}
@Override
public void onSharedPreferenceChanged(
final SharedPreferences sharedPreferences, final String key) {
Log.d(TAG,
"StatsActivity: onSharedPreferences changed " + key);
if (key != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (key.equals(getString(R.string.selected_track_key))) {
selectedTrackId =
sharedPreferences.getLong(
getString(R.string.selected_track_key),
-1);
checkLiveTrack();
restoreStats();
showUnknownLocation();
} else if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId =
sharedPreferences.getLong(
getString(R.string.recording_track_key),
-1);
checkLiveTrack();
restoreStats();
showUnknownLocation();
} else if (key.equals(getString(R.string.metric_units_key))) {
metricUnits =
sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
utils.updateUnits();
restoreStats();
} else if (key.equals(getString(R.string.report_speed_key))) {
displaySpeed =
sharedPreferences.getBoolean(
getString(R.string.report_speed_key),
true);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
utils.setSpeedLabel(
R.id.speed_label, R.string.speed, R.string.pace_label);
Log.w(TAG, "Setting speed labels");
utils.setSpeedLabels();
restoreStats();
}
}
});
}
public boolean onReportSpeedChanged(boolean displaySpeed) {
utils.setReportSpeed(displaySpeed);
updateLabels();
return true; // Reload data
}
private void updateLabels() {
runOnUiThread(new Runnable() {
@Override
public void run() {
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
utils.setSpeedLabels();
}
});
}
@Override
@@ -291,137 +196,12 @@ public class StatsActivity extends Activity
switch (item.getItemId()) {
case Constants.MENU_CURRENT_SEGMENT:
showCurrentSegment = !showCurrentSegment;
restoreStats();
// TODO: Re-read only the data that interests us
return true;
}
return super.onOptionsItemSelected(item);
}
private final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location l) {
if (selectedTrackIsRecording()) {
showLocation(l);
}
}
@Override
public void onProviderDisabled(String provider) {
// Do nothing
}
@Override
public void onProviderEnabled(String provider) {
// Do nothing
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Do nothing
}
};
/**
* Registers to receive location updates from the GPS location provider.
*/
private void registerLocationListener() {
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
LocationProvider gpsProvider =
locationManager.getProvider(Constants.GPS_PROVIDER);
if (gpsProvider == null) {
Toast.makeText(this, getString(R.string.error_no_gps_location_provider),
Toast.LENGTH_LONG).show();
return;
} else {
Log.d(TAG, "StatsActivity: Using location provider "
+ gpsProvider.getName());
}
locationManager.requestLocationUpdates(gpsProvider.getName(),
0/*minTime*/, 0/*minDist*/, locationListener);
}
}
/**
* Unregisters all location listener.
*/
private void unregisterLocationListener() {
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
locationManager.removeUpdates(locationListener);
}
}
/**
* @return true if the selected track is the currently recording track
*/
private boolean selectedTrackIsRecording() {
return MyTracks.getInstance().isRecording()
&& selectedTrackId == recordingTrackId;
}
/**
* Reads values for selected tracks from provider and update the UI.
*/
private void restoreStats() {
if (selectedTrackId < 0) {
utils.setAllToUnknown();
return;
}
Track track = providerUtils.getTrack(selectedTrackId);
if (track == null || track.getStatistics() == null) {
utils.setAllToUnknown();
return;
}
startTime = track.getStatistics().getStartTime();
if (!selectedTrackIsRecording()) {
utils.setTime(R.id.total_time_register,
track.getStatistics().getTotalTime());
}
utils.setAllStats(track.getStatistics());
}
/**
* Checks if this activity needs to update live track data or not.
* If so, make sure that:
* a) a thread keeps updating the total time
* b) a location listener is registered
* c) a content observer is registered
* Otherwise unregister listeners, observers, and kill update thread.
*/
private void checkLiveTrack() {
final boolean isRecording = selectedTrackIsRecording();
final boolean startThread =
(thread == null) && isRecording && activityOnTop;
final boolean killThread =
(thread != null) && (!isRecording || !activityOnTop);
if (startThread) {
thread = new UIUpdateThread();
thread.start();
getContentResolver().registerContentObserver(
TracksColumns.CONTENT_URI, false, observer);
getContentResolver().registerContentObserver(
WaypointsColumns.CONTENT_URI, false, observer);
registerLocationListener();
} else if (killThread) {
thread.interrupt();
thread = null;
getContentResolver().unregisterContentObserver(observer);
unregisterLocationListener();
}
}
public void updateTotalTime() {
if (selectedTrackIsRecording()) {
utils.setTime(R.id.total_time_register,
System.currentTimeMillis() - startTime);
}
}
/**
* Updates the given location fields (latitude, longitude, altitude) and all
* other fields.
@@ -441,4 +221,112 @@ public class StatsActivity extends Activity
utils.setUnknown(R.id.longitude_register);
utils.setUnknown(R.id.speed_register);
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
/*
* Checks if this activity needs to update live track data or not.
* If so, make sure that:
* a) a thread keeps updating the total time
* b) a location listener is registered
* c) a content observer is registered
* Otherwise unregister listeners, observers, and kill update thread.
*/
final boolean startThread = (thread == null) && isRecording;
final boolean killThread = (thread != null) && (!isRecording);
if (startThread) {
thread = new UIUpdateThread();
thread.start();
} else if (killThread) {
thread.interrupt();
thread = null;
}
if (track == null || track.getStatistics() == null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
utils.setAllToUnknown();
}
});
}
}
@Override
public void onCurrentLocationChanged(Location loc) {
if (dataHub.isRecordingSelected()) {
showLocation(loc);
}
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onProviderStateChange(ProviderState state) {
switch (state) {
case DISABLED:
case NO_FIX:
showUnknownLocation();
break;
}
}
@Override
public void onTrackUpdated(final Track track) {
runOnUiThread(new Runnable() {
@Override
public void run() {
startTime = track.getStatistics().getStartTime();
if (!dataHub.isRecordingSelected()) {
utils.setTime(R.id.total_time_register,
track.getStatistics().getTotalTime());
showUnknownLocation();
}
utils.setAllStats(track.getStatistics());
}
});
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
@Override
public void clearTrackPoints() {
// We don't care.
}
@Override
public void onNewTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSegmentSplit() {
// We don't care.
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
// We don't care.
}
}
@@ -607,7 +607,7 @@ public class TrackDataHub {
if (key.equals(RECORDING_TRACK_KEY)) {
recordingTrackId = sharedPreferences.getLong(RECORDING_TRACK_KEY, -1);
} else if (key.equals(MIN_REQUIRED_ACCURACY_KEY)) {
minRequiredAccuracy = sharedPreferences.getInt(RECORDING_TRACK_KEY,
minRequiredAccuracy = sharedPreferences.getInt(MIN_REQUIRED_ACCURACY_KEY,
Constants.DEFAULT_MIN_REQUIRED_ACCURACY);
} else if (key.equals(METRIC_UNITS_KEY)) {
useMetricUnits = sharedPreferences.getBoolean(METRIC_UNITS_KEY, true);
@@ -33,6 +33,7 @@ import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
import com.google.android.apps.mytracks.services.tasks.PeriodicTaskExecuter;
import com.google.android.apps.mytracks.services.tasks.SplitTask;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
@@ -190,8 +191,6 @@ public class TrackRecordingService extends Service implements LocationListener {
/**
* The frequency of status announcements.
*/
private int announcementFrequency = -1;
private ExecutorService executerServce;
/*
@@ -667,6 +666,8 @@ public class TrackRecordingService extends Service implements LocationListener {
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
setUpAnnouncer();
this.splitExecuter = new PeriodicTaskExecuter(this, new SplitTask());
prefManager = new PreferenceManager(this);
registerLocationListener();
@@ -702,32 +703,20 @@ public class TrackRecordingService extends Service implements LocationListener {
private void setUpAnnouncer() {
Log.d(TAG, "TrackRecordingService.setUpAnnouncer: "
+ announcementExecuter);
if (announcementFrequency != -1 && recordingTrackId != -1) {
handler.post(new Runnable() {
@Override
public void run() {
if (announcementExecuter == null) {
StatusAnnouncerFactory statusAnnouncerFactory =
new StatusAnnouncerFactory(ApiFeatures.getInstance());
PeriodicTask announcer = statusAnnouncerFactory.create(
TrackRecordingService.this);
if (announcer == null) {
return;
}
// TODO: Either use TaskExecuterManager everywhere, or get rid of it
announcementExecuter = new PeriodicTaskExecuter(
TrackRecordingService.this, announcer);
}
announcementExecuter.setTaskFrequency(announcementFrequency * 60000);
}
});
StatusAnnouncerFactory statusAnnouncerFactory =
new StatusAnnouncerFactory(ApiFeatures.getInstance());
PeriodicTask announcer = statusAnnouncerFactory.create(
TrackRecordingService.this);
if (announcer == null) {
return;
}
announcementExecuter = new PeriodicTaskExecuter(
TrackRecordingService.this, announcer);
}
private void shutdownAnnouncer() {
Log.d(TAG, "TrackRecordingService.shutdownAnnouncer: "
+ announcementExecuter);
private void shutdownExecuters() {
Log.d(TAG, "TrackRecordingService.shutdownExecuters");
if (announcementExecuter != null) {
try {
announcementExecuter.shutdown();
@@ -735,6 +724,13 @@ public class TrackRecordingService extends Service implements LocationListener {
announcementExecuter = null;
}
}
if (splitExecuter != null) {
try {
splitExecuter.shutdown();
} finally {
splitExecuter = null;
}
}
}
@Override
@@ -750,11 +746,7 @@ public class TrackRecordingService extends Service implements LocationListener {
timer.cancel();
timer.purge();
unregisterLocationListener();
shutdownAnnouncer();
announcementExecuter.shutdown();
announcementExecuter = null;
splitExecuter.shutdown();
splitExecuter = null;
shutdownExecuters();
if (sensorManager != null) {
sensorManager.shutdown();
sensorManager = null;
@@ -1109,12 +1101,9 @@ public class TrackRecordingService extends Service implements LocationListener {
waypointStatsBuilder = new TripStatisticsBuilder(startTime);
waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
setUpAnnouncer();
length = 0;
showNotification();
registerLocationListener();
announcementExecuter.restore();
splitExecuter.restore();
sensorManager = SensorManagerFactory.getSensorManager(this);
if (sensorManager != null) {
sensorManager.onStartTrack();
@@ -1129,6 +1118,8 @@ public class TrackRecordingService extends Service implements LocationListener {
// Notify the world that we're now recording.
sendTrackBroadcast(
R.string.track_started_broadcast_action, recordingTrackId);
announcementExecuter.restore();
splitExecuter.restore();
return recordingTrackId;
}
@@ -1139,7 +1130,7 @@ public class TrackRecordingService extends Service implements LocationListener {
throw new IllegalStateException("No recording track in progress!");
}
shutdownAnnouncer();
shutdownExecuters();
isRecording = false;
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
if (recordingTrack != null) {
@@ -1199,19 +1190,6 @@ public class TrackRecordingService extends Service implements LocationListener {
this.recordingTrackId = recordingTrackId;
}
public int getAnnouncementFrequency() {
return announcementFrequency;
}
public void setAnnouncementFrequency(int announcementFrequency) {
this.announcementFrequency = announcementFrequency;
if (announcementFrequency == -1) {
shutdownAnnouncer();
} else {
setUpAnnouncer();
}
}
public int getMaxRecordingDistance() {
return maxRecordingDistance;
}
@@ -1257,12 +1235,24 @@ public class TrackRecordingService extends Service implements LocationListener {
this.autoResumeTrackTimeout = autoResumeTrackTimeout;
}
public void setAnnouncementFrequency(int announcementFrequency) {
if (announcementExecuter != null) {
announcementExecuter.setTaskFrequency(announcementFrequency);
}
}
public void setSplitFrequency(int frequency) {
splitExecuter.setTaskFrequency(frequency);
if (splitExecuter != null) {
splitExecuter.setTaskFrequency(frequency);
}
}
public void setMetricUnits(boolean metric) {
announcementExecuter.setMetricUnits(metric);
splitExecuter.setMetricUnits(metric);
if (announcementExecuter != null) {
announcementExecuter.setMetricUnits(metric);
}
if (splitExecuter != null) {
splitExecuter.setMetricUnits(metric);
}
}
}
@@ -61,10 +61,29 @@ public class PeriodicTaskExecuter {
* Restores the manager.
*/
public void restore() {
if ((isTimeFrequency()) && (timerExecuter != null)) {
timerExecuter.scheduleTask(taskFrequency * 60000);
// TODO: Decouple service from this class once and forever.
if (!service.isRecording()) {
return;
}
if (!isTimeFrequency()) {
if (timerExecuter != null) {
timerExecuter.shutdown();
timerExecuter = null;
}
}
if (taskFrequency == 0) {
return;
}
if (isTimeFrequency()) {
if (timerExecuter == null) {
timerExecuter = new TimerTaskExecuter(task, service);
}
timerExecuter.scheduleTask(taskFrequency * 60000);
} else {
// For distance based splits.
calculateNextTaskDistance();
}
calculateNextTaskDistance();
}
/**
@@ -140,30 +159,7 @@ public class PeriodicTaskExecuter {
public void setTaskFrequency(int taskFrequency) {
Log.d(TAG, "setTaskFrequency: taskFrequency = " + taskFrequency);
this.taskFrequency = taskFrequency;
// TODO: Decouple service from this class once and forever.
if (!service.isRecording()) {
return;
}
if (!isTimeFrequency()) {
if (timerExecuter != null) {
timerExecuter.shutdown();
timerExecuter = null;
}
}
if (taskFrequency == 0) {
return;
}
if (isTimeFrequency()) {
if (timerExecuter == null) {
timerExecuter = new TimerTaskExecuter(task, service);
}
timerExecuter.scheduleTask(taskFrequency * 60000);
} else {
// For distance based splits.
calculateNextTaskDistance();
}
restore();
}
public void setMetricUnits(boolean metricUnits) {
@@ -24,7 +24,7 @@ import com.google.android.apps.mytracks.services.TrackRecordingService;
* A simple task to insert statistics markers every n minutes.
* @author Sandor Dornbush
*/
public class TimeSplitTask implements PeriodicTask {
public class SplitTask implements PeriodicTask {
@Override
public void run(TrackRecordingService service) {
@@ -19,7 +19,6 @@ package com.google.android.apps.mytracks.services.tasks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
@@ -177,7 +176,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
// @VisibleForTesting
protected String getAnnouncement(TripStatistics stats) {
SharedPreferences preferences =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
boolean metricUnits = true;
boolean reportSpeed = true;
if (preferences != null) {