From 8d3ab8f93b044e1ef0c6568f073e577ad7b8714d Mon Sep 17 00:00:00 2001 From: Sandor Dornbush Date: Tue, 5 Apr 2011 21:34:47 -0700 Subject: [PATCH] Refactor periodic tasks. --- MyTracks/res/values-ca/arrays.xml | 12 +- MyTracks/res/values-de/arrays.xml | 9 +- MyTracks/res/values-es/arrays.xml | 22 +- MyTracks/res/values-fr/arrays.xml | 25 +- MyTracks/res/values-it/arrays.xml | 22 +- MyTracks/res/values-ja/arrays.xml | 29 +- MyTracks/res/values-pl/arrays.xml | 24 +- MyTracks/res/values-pt-rBR/arrays.xml | 55 +- MyTracks/res/values-sv/arrays.xml | 23 +- MyTracks/res/values-zh-rCN/arrays.xml | 23 +- MyTracks/res/values/arrays.xml | 143 +-- .../res/values/arrays_do_not_translate.xml | 122 +++ MyTracks/res/xml/preferences.xml | 4 +- .../android/apps/mytracks/ChartActivity.java | 531 +++------- .../android/apps/mytracks/MapActivity.java | 907 ++++-------------- .../apps/mytracks/SettingsActivity.java | 16 +- .../android/apps/mytracks/StatsActivity.java | 422 +++----- .../android/apps/mytracks/TrackDataHub.java | 2 +- .../services/TrackRecordingService.java | 90 +- .../services/tasks/PeriodicTaskExecuter.java | 50 +- .../{TimeSplitTask.java => SplitTask.java} | 2 +- .../services/tasks/StatusAnnouncerTask.java | 3 +- .../StatusAnnouncerFactoryTest.java | 2 +- .../{ => tasks}/StatusAnnouncerTaskTest.java | 2 +- 24 files changed, 767 insertions(+), 1773 deletions(-) create mode 100644 MyTracks/res/values/arrays_do_not_translate.xml rename MyTracks/src/com/google/android/apps/mytracks/services/tasks/{TimeSplitTask.java => SplitTask.java} (95%) rename MyTracksTest/src/com/google/android/apps/mytracks/services/{ => tasks}/StatusAnnouncerFactoryTest.java (97%) rename MyTracksTest/src/com/google/android/apps/mytracks/services/{ => tasks}/StatusAnnouncerTaskTest.java (99%) diff --git a/MyTracks/res/values-ca/arrays.xml b/MyTracks/res/values-ca/arrays.xml index 2eddecf5b..2bba332fa 100644 --- a/MyTracks/res/values-ca/arrays.xml +++ b/MyTracks/res/values-ca/arrays.xml @@ -112,9 +112,9 @@ limitations under the License. 3.1 miles (senyal GPS pobre) - - - Sense divisió + + + Inactiu 100 km 50 km 25 km @@ -130,8 +130,8 @@ limitations under the License. 1 min - - No splitting + + Inactiu 100 mi 50 mi 25 mi @@ -286,6 +286,8 @@ limitations under the License. Cap + ANT+ Sensors + SRM ANT+ Bridge Zephyr Bluetooth diff --git a/MyTracks/res/values-de/arrays.xml b/MyTracks/res/values-de/arrays.xml index ed7915fc8..f33ae9fe1 100644 --- a/MyTracks/res/values-de/arrays.xml +++ b/MyTracks/res/values-de/arrays.xml @@ -48,7 +48,6 @@ limitations under the License. 328 Fuß - 50 m @@ -113,9 +112,9 @@ limitations under the License. 3,1 Meilen (sehr schlechtes GPS-Signal) - - - Nicht zerteilen + + + Aus 100 km 50 km 25 km @@ -132,7 +131,7 @@ limitations under the License. - Nicht zerteilen + Aus 100 Meilen 50 Meilen 25 Meilen diff --git a/MyTracks/res/values-es/arrays.xml b/MyTracks/res/values-es/arrays.xml index 25bd36439..2c9ac030a 100644 --- a/MyTracks/res/values-es/arrays.xml +++ b/MyTracks/res/values-es/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Apagado - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - 1 m @@ -112,9 +100,9 @@ limitations under the License. 3.1 miles (señal GPS débil) - - - No dividir + + + Apagado 100 km 50 km 25 km @@ -130,8 +118,8 @@ limitations under the License. 1 min - - No dividir + + Apagado 100 mi 50 mi 25 mi diff --git a/MyTracks/res/values-fr/arrays.xml b/MyTracks/res/values-fr/arrays.xml index 0c9eedf72..18740a364 100644 --- a/MyTracks/res/values-fr/arrays.xml +++ b/MyTracks/res/values-fr/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Absent - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - 1 m @@ -112,10 +100,9 @@ limitations under the License. 3.1 miles (mauvais signal GPS) - - - - Pas de séparation + + + Absent 100 km 50 km 25 km @@ -131,8 +118,8 @@ limitations under the License. 1 min - - Pas de séparation + + Absent 100 mi 50 mi 25 mi @@ -310,5 +297,5 @@ limitations under the License. Pont SRM ANT+ Bluetooth Zephyr - + diff --git a/MyTracks/res/values-it/arrays.xml b/MyTracks/res/values-it/arrays.xml index 4b8e02cb5..9974d2fc2 100644 --- a/MyTracks/res/values-it/arrays.xml +++ b/MyTracks/res/values-it/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Disattivato - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - 1 m @@ -112,9 +100,9 @@ limitations under the License. 3.1 mi (segnale GPS debole) - - - Non dividere + + + Disattivato 100 km 50 km 25 km @@ -130,8 +118,8 @@ limitations under the License. 1 min - - Non dividere + + Disattivato 100 mi 50 mi 25 mi diff --git a/MyTracks/res/values-ja/arrays.xml b/MyTracks/res/values-ja/arrays.xml index 582056996..31e45abee 100644 --- a/MyTracks/res/values-ja/arrays.xml +++ b/MyTracks/res/values-ja/arrays.xml @@ -15,19 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - 不使用 - 1 分 - 2 分 - 5 分 - 10 分 - 15 分 - 30 分 - 60 分 - - - + 1 m 2 m @@ -48,7 +36,6 @@ limitations under the License. 328 ft - 50 m @@ -68,7 +55,6 @@ limitations under the License. 3.1 mi - 電源節約 (動的制御) @@ -89,7 +75,6 @@ limitations under the License. 30 分 - 10 m (GPS信号強度良好) @@ -115,10 +100,9 @@ limitations under the License. 3.1 mi (GPS信号強度不良) - - - - 分割なし + + + 不使用 100 km 50 km 25 km @@ -134,8 +118,8 @@ limitations under the License. 1 分 - - 分割なし + + 不使用 100 mi 50 mi 25 mi @@ -294,4 +278,5 @@ limitations under the License. SRM ANT+ ブリッジ Zephyr Bluetooth + diff --git a/MyTracks/res/values-pl/arrays.xml b/MyTracks/res/values-pl/arrays.xml index 80c7d0dbd..bf55da1a4 100644 --- a/MyTracks/res/values-pl/arrays.xml +++ b/MyTracks/res/values-pl/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Brak - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - 1 m @@ -112,9 +100,9 @@ limitations under the License. 3.1 mili (słaby sygnał GPS) - - - Brak dzielenia + + + Brak 100 km 50 km 25 km @@ -130,8 +118,8 @@ limitations under the License. 1 min - - Brak dzielenia + + Brak 100 mi 50 mi 25 mi @@ -146,7 +134,7 @@ limitations under the License. 2 min 1 min - + Nigdy diff --git a/MyTracks/res/values-pt-rBR/arrays.xml b/MyTracks/res/values-pt-rBR/arrays.xml index 8f442d0fd..60090ae40 100644 --- a/MyTracks/res/values-pt-rBR/arrays.xml +++ b/MyTracks/res/values-pt-rBR/arrays.xml @@ -15,21 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Desligado - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - - - + 1 m 2 m 3 m @@ -37,9 +24,9 @@ limitations under the License. 5 m (recomendada) 10 m 100 m - + - + 3 pés 6 pés 10 pés @@ -47,31 +34,29 @@ limitations under the License. 16 pés (recomendado) 33 pés 328 pés - - + - + 50 m 100 m 200 m (recomendado) 500 m 1000 m 5000 m - + - + 164 pés 328 pés 656 pés (recomendado) 1640 pés 0.6 milhas 3.1 milhas - - + - + economia de bateria (adaptativo) alta precisão (adaptativo) máxima (recomendada) @@ -88,11 +73,10 @@ limitations under the License. 10 min 15 min 30 min - - + - + 10 m (sinal de GPS excelente) 20 m 50 m @@ -102,9 +86,9 @@ limitations under the License. 1000 m 2000 m 5000 m (sinal de GPS ruim) - + - + 33 pés (sinal de GPS excelente) 66 pés 165 pés @@ -114,12 +98,11 @@ limitations under the License. 0.6 milhas 1.2 milhas 3.1 milhas (sinal de GPS ruim) - + - - - - Sem divisões + + + Desligado 100 km 50 km 25 km @@ -135,8 +118,8 @@ limitations under the License. 1 min - - Sem divisões + + Desligado 100 mi 50 mi 25 mi diff --git a/MyTracks/res/values-sv/arrays.xml b/MyTracks/res/values-sv/arrays.xml index eb24d0ef3..fef8a4ec9 100644 --- a/MyTracks/res/values-sv/arrays.xml +++ b/MyTracks/res/values-sv/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Av - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - 1 m @@ -67,7 +55,6 @@ limitations under the License. 3.1 miles - batterioptimerad (anpassningsbar) @@ -113,9 +100,9 @@ limitations under the License. 3.1 miles (dålig GPS-signal) - - - Ingen delning + + + Av 100 km 50 km 25 km @@ -131,8 +118,8 @@ limitations under the License. 1 min - - Ingen delning + + Av 100 mi 50 mi 25 mi diff --git a/MyTracks/res/values-zh-rCN/arrays.xml b/MyTracks/res/values-zh-rCN/arrays.xml index 48f15e956..e3108f9e2 100644 --- a/MyTracks/res/values-zh-rCN/arrays.xml +++ b/MyTracks/res/values-zh-rCN/arrays.xml @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - 关闭 - 1 分钟 - 2 分钟 - 5 分钟 - 10 分钟 - 15 分钟 - 30 分钟 - 60 分钟 - - 1 米 @@ -112,9 +100,9 @@ limitations under the License. 3.1 英里 (糟糕的 GPS 信号) - - - 不分割 + + + 关闭 100 千米 50 千米 25 千米 @@ -130,8 +118,8 @@ limitations under the License. 1 分钟 - - 不分割 + + 关闭 100 英里 50 英里 25 英里 @@ -155,6 +143,7 @@ limitations under the License. 15 分钟 总是 + 未知 diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml index d50dfd701..783668362 100644 --- a/MyTracks/res/values/arrays.xml +++ b/MyTracks/res/values/arrays.xml @@ -15,31 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - Off - 1 min - 2 min - 5 min - 10 min - 15 min - 30 min - 60 min - - - - - -1 - 1 - 2 - 5 - 10 - 15 - 30 - 60 - - - 1 m @@ -61,18 +36,6 @@ limitations under the License. 328 ft - - - 1 - 2 - 3 - 4 - 5 - 10 - 100 - - - 50 m @@ -92,17 +55,6 @@ limitations under the License. 3.1 miles - - - 50 - 100 - 200 - 500 - 1000 - 5000 - - - battery miser (adaptive) @@ -123,27 +75,6 @@ limitations under the License. 30 min - - - -2 - -1 - 0 - 2 - 3 - 4 - 5 - 10 - 20 - 30 - 60 - 120 - 300 - 480 - 900 - 1800 - - - 10 m (excellent GPS signal) @@ -169,23 +100,9 @@ limitations under the License. 3.1 miles (poor GPS signal) - - - 10 - 20 - 50 - 100 - 200 - 500 - 1000 - 2000 - 5000 - - - - - - No splitting + + + Off 100 km 50 km 25 km @@ -201,8 +118,8 @@ limitations under the License. 1 min - - No splitting + + Off 100 mi 50 mi 25 mi @@ -218,24 +135,6 @@ limitations under the License. 1 min - - - 0 - -100 - -50 - -25 - -10 - -5 - -1 - 60 - 30 - 15 - 10 - 5 - 2 - 1 - - Never @@ -245,16 +144,6 @@ limitations under the License. Always - - - 0 - 5 - 10 - 15 - -1 - - - unknown @@ -383,14 +272,6 @@ limitations under the License. way point - - - GPX - KML - CSV - TCX - - None ANT+ Sensors @@ -398,18 +279,4 @@ limitations under the License. Zephyr Bluetooth - - - @string/none - @string/ant_sensor_type - @string/srm_ant_bridge_sensor_type - @string/zephyr_sensor_type - - - - - @string/ant_sensor_type - @string/srm_ant_bridge_sensor_type - - diff --git a/MyTracks/res/values/arrays_do_not_translate.xml b/MyTracks/res/values/arrays_do_not_translate.xml new file mode 100644 index 000000000..d7d383a78 --- /dev/null +++ b/MyTracks/res/values/arrays_do_not_translate.xml @@ -0,0 +1,122 @@ + + + + + + 10 + 20 + 50 + 100 + 200 + 500 + 1000 + 2000 + 5000 + + + + 1 + 2 + 3 + 4 + 5 + 10 + 100 + + + + 1 + 2 + 3 + 4 + 5 + 10 + 100 + + + + 50 + 100 + 200 + 500 + 1000 + 5000 + + + + -2 + -1 + 0 + 2 + 3 + 4 + 5 + 10 + 20 + 30 + 60 + 120 + 300 + 480 + 900 + 1800 + + + + 0 + -100 + -50 + -25 + -10 + -5 + -1 + 60 + 30 + 15 + 10 + 5 + 2 + 1 + + + + 0 + 5 + 10 + 15 + -1 + + + + GPX + KML + CSV + TCX + + + + @string/none + @string/ant_sensor_type + @string/srm_ant_bridge_sensor_type + @string/zephyr_sensor_type + + + + @string/ant_sensor_type + @string/srm_ant_bridge_sensor_type + + diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml index e4d0b093f..d964f124b 100644 --- a/MyTracks/res/xml/preferences.xml +++ b/MyTracks/res/xml/preferences.xml @@ -48,8 +48,8 @@ limitations under the License. android:key="@string/split_frequency_key" android:title="@string/settings_split_frequency" android:summary="@string/settings_split_frequency_summary" - android:entries="@array/split_frequency_options" - android:entryValues="@array/split_frequency_values" + android:entries="@array/task_frequency_options" + android:entryValues="@array/task_frequency_values" android:defaultValue="0" /> pendingPoints = new ArrayList(); + 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 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 readPointsToList(Track track) { - Cursor cursor = null; - long lastLocationRead = lastSeenLocationId; - int points = 0; - int chartSamplingFrequency = getSamplingFrequency(track); - ArrayList result = new ArrayList(); - // 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 } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java index 71d382c66..7a7f6e950 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java @@ -19,81 +19,53 @@ import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; 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.services.tasks.StatusAnnouncerFactory; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.GeoRect; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.maps.GeoPoint; -import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.mytracks.R; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.database.Cursor; -import android.hardware.GeomagneticField; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; 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.os.HandlerThread; import android.provider.Settings; import android.util.Log; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.SubMenu; import android.view.View; -import android.view.Window; -import android.view.ContextMenu.ContextMenuInfo; import android.view.View.OnCreateContextMenuListener; +import android.view.Window; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; /** * The map view activity of the MyTracks application. * * @author Leif Hendrik Wilden + * @author Rodrigo Damazio */ -public class MyTracksMap extends MapActivity +public class MapActivity extends com.google.android.maps.MapActivity implements View.OnTouchListener, View.OnClickListener, - SharedPreferences.OnSharedPreferenceChangeListener { - private static final int TRACKPOINT_BUFFER_SIZE = 1024; + TrackDataListener { // Saved instance state keys: // --------------------------- - public static final String KEY_CURRENT_LOCATION = "currentLocation"; - public static final String KEY_KEEP_MY_LOCATION_VISIBLE = - "keepMyLocationVisible"; - public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix"; + private static final String KEY_CURRENT_LOCATION = "currentLocation"; + private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible"; - /** - * The ID of the currently selected track (or -1 if nothing selected). - */ - private long selectedTrackId = -1; - - /** - * The id of the currently recording track. - */ - private long recordingTrackId = -1; + private TrackDataHub dataHub; /** * True if the map should be scrolled so that the pointer is always in the @@ -101,152 +73,18 @@ public class MyTracksMap extends MapActivity */ private boolean keepMyLocationVisible; - /** - * Id of the first location that was seen when reading tracks from the - * provider. - */ - private long firstSeenLocationId = -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 mapOverlay was updated. - */ - private long lastSeenLocationId = -1; - - /** - * Magnetic variation. - */ - private double variation; - - /** - * From the shared preferences. - */ - private int minRequiredAccuracy = - MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY; - - /** - * True, if the application thinks it has a good fix, i.e. accuracy is better - * than the required accuracy. - */ - private boolean haveGoodFix; - /** * The current pointer location. + * This is kept to quickly center on it when the user requests. */ private Location currentLocation; - /** - * A thread with a looper. Post to updateTrackHandler to execute - * {@link Runnable}s on this thread. - */ - private HandlerThread updateTrackThread; - - /** - * Handler for updateTrackThread. - */ - private Handler updateTrackHandler; - - private MyTracksProviderUtils providerUtils; - - private SharedPreferences sharedPreferences; - - /** - * A runnable that updates the track from the provider (looking for points - * added after "lastSeenLocationId"). - */ - private final Runnable updateTrackRunnable = new Runnable() { - @Override - public void run() { - if (!isATrackSelected()) { - return; - } - - readAllNewTrackPoints(); - } - }; - - /** - * A runnable that restores all track points from the provider. - */ - private Runnable restoreTrackRunnable = new Runnable() { - @Override - public void run() { - if (!isATrackSelected()) { - return; - } - - mapOverlay.clearPoints(); - firstSeenLocationId = -1; - lastSeenLocationId = -1; - readAllNewTrackPoints(); - } - }; - - /** - * A runnable that restores all waypoints from the provider. - */ - private final Runnable restoreWaypointsRunnable = new Runnable() { - @Override - public void run() { - if (!isATrackSelected()) { - return; - } - - Cursor cursor = null; - mapOverlay.clearWaypoints(); - try { - // We will silently drop extra waypoints to make the app responsive. - // TODO: Try to only load the waypoints in the view port. - cursor = providerUtils.getWaypointsCursor( - selectedTrackId, 0, - Constants.MAX_DISPLAYED_WAYPOINTS_POINTS); - if (cursor != null && cursor.moveToFirst()) { - do { - Waypoint waypoint = providerUtils.createWaypoint(cursor); - if (MyTracksUtils.isValidLocation(waypoint.getLocation())) { - mapOverlay.addWaypoint(waypoint); - } - } while (cursor.moveToNext()); - } - } catch (RuntimeException e) { - Log.w(TAG, "Caught an unexpected exception.", e); - } finally { - if (cursor != null) { - cursor.close(); - } - } - mapView.postInvalidate(); - } - }; - - /** - * A runnable intended to be posted to the {@code #updateTrackThread} after - * the selected track changes. It will post to the UI thread to update - * the screen elements and move the map to show the selected track. - */ - private final Runnable setSelectedTrackRunnable = new Runnable() { - @Override - public void run() { - uiHandler.post(new Runnable() { - public void run() { - showTrack(selectedTrackId); - mapOverlay.setTrackDrawingEnabled(isATrackSelected()); - mapOverlay.setShowEndMarker(!isRecordingSelected()); - mapView.invalidate(); - busyPane.setVisibility(View.GONE); - updateOptionsButton(); - } - }); - } - }; - // UI elements: // ------------- private RelativeLayout screen; private MapView mapView; - private MyTracksOverlay mapOverlay; + private MapOverlay mapOverlay; private LinearLayout messagePane; private TextView messageText; private LinearLayout busyPane; @@ -255,14 +93,6 @@ public class MyTracksMap extends MapActivity private MenuItem myLocation; private MenuItem toggleLayers; - private SensorManager sensorManager; - private LocationManager locationManager; - private ContentObserver observer; - private ContentObserver waypointObserver; - - /** Handler for callbacks to the UI thread */ - private final Handler uiHandler = new Handler(); - /** * We are not displaying driving directions. Just an arbitrary track that is * not associated to any licensed mapping data. Therefore it should be okay to @@ -287,7 +117,7 @@ public class MyTracksMap extends MapActivity @Override public void onCreate(Bundle bundle) { - Log.d(TAG, "MyTracksMap.onCreate"); + Log.d(TAG, "MapActivity.onCreate"); super.onCreate(bundle); // The volume we want to control is the Text-To-Speech volume @@ -295,7 +125,7 @@ public class MyTracksMap extends MapActivity new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); setVolumeControlStream(volumeStream); - providerUtils = MyTracksProviderUtils.Factory.get(this); + dataHub = MyTracks.getInstance().getDataHub(); // We don't need a window title bar: requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -310,9 +140,10 @@ public class MyTracksMap extends MapActivity screen = (RelativeLayout) findViewById(R.id.screen); mapView = (MapView) findViewById(R.id.map); mapView.requestFocus(); - mapOverlay = new MyTracksOverlay(this); + mapOverlay = new MapOverlay(this); mapView.getOverlays().add(mapOverlay); mapView.setOnTouchListener(this); + mapView.setBuiltInZoomControls(true); messagePane = (LinearLayout) findViewById(R.id.messagepane); messageText = (TextView) findViewById(R.id.messagetext); busyPane = (LinearLayout) findViewById(R.id.busypane); @@ -320,186 +151,18 @@ public class MyTracksMap extends MapActivity optionsBtn.setOnCreateContextMenuListener(contextMenuListener); optionsBtn.setOnClickListener(this); - - setupZoomControls(); - - // Get the sensor and location managers: - sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); - locationManager = - (LocationManager) getSystemService(Context.LOCATION_SERVICE); - - updateTrackThread = new HandlerThread("updateTrackThread"); - 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, "MyTracksMap: ContentObserver.onChange"); - if (!isRecordingSelected()) { - // 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 (!isATrackSelected()) { - return; - } - updateTrackHandler.post(restoreWaypointsRunnable); - super.onChange(selfChange); - } - }; - - // Read shared preferences and register change listener. - sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); - if (sharedPreferences != null) { - reloadSharedPreferences(sharedPreferences, null); - updateOptionsButton(); - sharedPreferences.registerOnSharedPreferenceChangeListener(this); - } - } - - @Override - protected void onDestroy() { - Log.d(TAG, "MyTracksMap.onDestroy"); - - if (updateTrackThread != null) { - ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread( - updateTrackThread); - } - if (sharedPreferences != null) { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); - } - - super.onDestroy(); - } - - /** - * Returns whether there's a track currently selected for display. - */ - private boolean isATrackSelected() { - return selectedTrackId >= 0; - } - - /** - * Returns whether we're currently recording the same track that's selected - * for display. - */ - private boolean isRecordingSelected() { - return isATrackSelected() && selectedTrackId == recordingTrackId; - } - - protected void setupZoomControls() { - mapView.setBuiltInZoomControls(true); - } - - @Override - protected void onStart() { - // Called after onCreate or onStop. - // Will be followed by onRestart. - Log.d(TAG, "MyTracksMap.onStart"); - super.onStart(); - } - - @Override - protected void onStop() { - // Called when activity is no longer visible to user. - // Next either onStart, onDestroy or nothing will be called. - // This method may never be called in low memory situations. - Log.d(TAG, "MyTracksMap.onStop"); - super.onStop(); - } - - @Override - protected void onRestart() { - // Called when the current activity is being re-displayed. - // Will be followed by onResume. - Log.d(TAG, "MyTracksMap.onRestart"); - super.onRestart(); - } - - @Override - protected void onPause() { - // Called when activity is going into the background, but has not (yet) been - // killed. Shouldn't block longer than approx. 2 seconds. - Log.d(TAG, "MyTracksMap.onPause"); - unregisterLocationAndSensorListeners(); - unregisterContentObservers(); - super.onPause(); - } - - @Override - protected void onResume() { - // Called when the current activity is being displayed or re-displayed - // to the user. - Log.d(TAG, "MyTracksMap.onResume"); - super.onResume(); - - // Reload all preferences as they might have changed since last run. - reloadSharedPreferences(sharedPreferences, null); - - // Make sure any updates that might have happened are propagated to the - // Map overlay: - observer.onChange(false); - waypointObserver.onChange(false); - - registerContentObservers(); - registerLocationAndSensorListeners(); - - if (locationManager.isProviderEnabled(Constants.GPS_PROVIDER)) { - messageText.setText(R.string.wait_for_fix); - messagePane.setOnClickListener(null); - } else { - messageText.setText(R.string.status_enable_gps); - messagePane.setVisibility(View.VISIBLE); - messagePane.setOnClickListener(this); - screen.requestLayout(); - } - - // While this activity was paused the user may have deleted the selected - // track. In that case the map overlay needs to be cleared: - if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) { - // The recording track must have been deleted meanwhile. - mapOverlay.setTrackDrawingEnabled(false); - mapView.invalidate(); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - Log.d(TAG, "MyTracksMap.onSaveInstanceState"); - outState.putBoolean(KEY_HAVE_GOOD_FIX, haveGoodFix); - outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible); - if (currentLocation != null) { - outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation); - } - super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle bundle) { - Log.d(TAG, "MyTracksMap.onRestoreInstanceState"); + Log.d(TAG, "MapActivity.onRestoreInstanceState"); if (bundle != null) { super.onRestoreInstanceState(bundle); - haveGoodFix = bundle.getBoolean(KEY_HAVE_GOOD_FIX, false); keepMyLocationVisible = bundle.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false); if (bundle.containsKey(KEY_CURRENT_LOCATION)) { currentLocation = (Location) bundle.getParcelable(KEY_CURRENT_LOCATION); if (currentLocation != null) { - setVariation(currentLocation); showCurrentLocation(); } } else { @@ -508,98 +171,42 @@ public class MyTracksMap extends MapActivity } } + @Override + protected void onStart() { + Log.d(TAG, "MapActivity.onStart"); + super.onStart(); + + dataHub.registerTrackDataListener(this); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + Log.d(TAG, "MapActivity.onSaveInstanceState"); + outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible); + if (currentLocation != null) { + outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation); + } + super.onSaveInstanceState(outState); + } + + @Override + protected void onStop() { + Log.d(TAG, "MapActivity.onStop"); + + dataHub.unregisterTrackDataListener(this); + + super.onStop(); + } + // Utility functions: // ------------------- - /** - * Toggles between satellite and map view. - */ - public void toggleLayer() { - mapView.setSatellite(!mapView.isSatellite()); - } - - /** - * Registers to receive location updates from the GPS location provider and - * sensor updated from the compass. - */ - void registerLocationAndSensorListeners() { - if (locationManager != null) { - LocationProvider gpsProvider = - locationManager.getProvider(Constants.GPS_PROVIDER); - if (gpsProvider == null) { - alert(getString(R.string.error_no_gps_location_provider)); - return; - } else { - Log.d(TAG, "MyTracksMap: Using location provider " - + gpsProvider.getName()); - } - locationManager.requestLocationUpdates(gpsProvider.getName(), - 0 /*minTime*/, 0 /*minDist*/, locationListener); - try { - locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - 1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener); - } catch (RuntimeException e) { - // If anything at all goes wrong with getting a cell location do not - // abort. Cell location is not essential to this app. - Log.w(TAG, - "Could not register network location listener."); - } - } - if (sensorManager == null) { - return; - } - Sensor compass = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); - if (compass == null) { - return; - } - Log.d(TAG, - "MyTracksMap: Now registering sensor listeners."); - sensorManager.registerListener( - sensorListener, compass, SensorManager.SENSOR_DELAY_UI); - } - - /** - * Unregisters all location and sensor listeners - */ - void unregisterLocationAndSensorListeners() { - if (locationManager != null) { - Log.d(TAG, - "MyTracksMap: Now unregistering location listeners."); - locationManager.removeUpdates(locationListener); - } - if (sensorManager != null) { - Log.d(TAG, - "MyTracksMap: Now unregistering sensor listeners."); - sensorManager.unregisterListener(sensorListener); - } - } - - /** - * Registers 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); - } - - /** - * Unregisters the content observer for the map overlay. - */ - private void unregisterContentObservers() { - getContentResolver().unregisterContentObserver(observer); - getContentResolver().unregisterContentObserver(waypointObserver); - } - /** * Shows the options button if a track is selected, or hide it if not. */ - private void updateOptionsButton() { + private void updateOptionsButton(boolean trackSelected) { optionsBtn.setVisibility( - isATrackSelected() ? View.VISIBLE : View.INVISIBLE); + trackSelected ? View.VISIBLE : View.INVISIBLE); } /** @@ -628,7 +235,7 @@ public class MyTracksMap extends MapActivity GeoRect r = new GeoRect(center, latSpan, lonSpan); r.top += margin; - GeoPoint geoPoint = MyTracksUtils.getGeoPoint(location); + GeoPoint geoPoint = LocationUtils.getGeoPoint(location); return r.contains(geoPoint); } @@ -641,25 +248,30 @@ public class MyTracksMap extends MapActivity return; } mapOverlay.setMyLocation(currentLocation); - mapView.invalidate(); + mapView.postInvalidate(); if (keepMyLocationVisible && !locationIsVisible(currentLocation)) { + GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation); MapController controller = mapView.getController(); - GeoPoint geoPoint = MyTracksUtils.getGeoPoint(currentLocation); controller.animateTo(geoPoint); } + + } + + @Override + public void onTrackUpdated(Track track) { + // We don't care. } /** * Zooms and pans the map so that the given track is visible. * - * @param trackId a given track ID + * @param track the track */ - public void showTrack(long trackId) { + private void zoomMapToBoundaries(Track track) { if (mapView == null) { return; } - Track track = providerUtils.getTrack(trackId); if (track == null || track.getNumberOfPoints() < 2) { return; } @@ -677,7 +289,7 @@ public class MyTracksMap extends MapActivity GeoPoint center = new GeoPoint( bottom + latSpanE6 / 2, left + lonSpanE6 / 2); - if (MyTracksUtils.isValidGeoPoint(center)) { + if (LocationUtils.isValidGeoPoint(center)) { mapView.getController().setCenter(center); mapView.getController().zoomToSpan(latSpanE6, lonSpanE6); } @@ -688,6 +300,7 @@ public class MyTracksMap extends MapActivity * Zooms and pans the map so that the given waypoint is visible. */ public void showWaypoint(long waypointId) { + MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(this); Waypoint wpt = providerUtils.getWaypoint(waypointId); if (wpt != null && wpt.getLocation() != null) { keepMyLocationVisible = false; @@ -700,86 +313,29 @@ public class MyTracksMap extends MapActivity } } - /** - * Sets the selected track and zoom and pan the map so that it is visible. - * - * @param trackId a given track id - */ - public void setSelectedTrack(final long trackId) { - Log.d(TAG, "MyTracksMap.setSelectedTrack: " - + "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId); + @Override + public void onSelectedTrackChanged(final Track track, final boolean isRecording) { + runOnUiThread(new Runnable() { + @Override + public void run() { + boolean trackSelected = track != null; + updateOptionsButton(trackSelected); - if (selectedTrackId == trackId) { - // Selected track did not change, nothing to do. - mapOverlay.setTrackDrawingEnabled(isATrackSelected()); - updateOptionsButton(); - mapView.invalidate(); - return; - } + mapOverlay.setTrackDrawingEnabled(trackSelected); - if (trackId < 0) { - // Remove selection. - selectedTrackId = -1; - mapOverlay.setTrackDrawingEnabled(false); - mapOverlay.clearWaypoints(); - updateOptionsButton(); - mapView.invalidate(); - return; - } - - busyPane.setVisibility(View.VISIBLE); - selectedTrackId = trackId; - loadSelectedTrack(); + if (trackSelected) { + busyPane.setVisibility(View.VISIBLE); + + zoomMapToBoundaries(track); + + mapOverlay.setShowEndMarker(!isRecording); + busyPane.setVisibility(View.GONE); + } + mapView.invalidate(); + } + }); } - private void loadSelectedTrack() { - updateTrackHandler.post(restoreTrackRunnable); - updateTrackHandler.post(restoreWaypointsRunnable); - updateTrackHandler.post(setSelectedTrackRunnable); - } - - /** - * Displays an alert for a few seconds. - * - * @param txt The text to be displayed - */ - public void alert(String txt) { - Toast.makeText(this, txt, Toast.LENGTH_LONG).show(); - } - - public void launchMyLocationSettings() { - startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); - } - - public void setVariation(Location location) { - long timestamp = location.getTime(); - if (timestamp == 0) { - // Hack for Samsung phones which don't populate the time field - timestamp = System.currentTimeMillis(); - } - - GeomagneticField field = new GeomagneticField( - (float) location.getLatitude(), - (float) location.getLongitude(), - (float) location.getAltitude(), - timestamp); - variation = field.getDeclination(); - - Log.d(TAG, - "MyTracksMap: Variation reset to " + variation + " degrees."); - } - - public MyTracksOverlay getMapOverlay() { - return mapOverlay; - } - - public MapView getMapView() { - return mapView; - } - - // Event listeners: - // ----------------- - private final OnCreateContextMenuListener contextMenuListener = new OnCreateContextMenuListener() { @Override @@ -788,7 +344,7 @@ public class MyTracksMap extends MapActivity menu.setHeaderTitle(R.string.tracklist_this_track); menu.add(0, Constants.MENU_EDIT, 0, R.string.tracklist_edit_track); - if (!isRecordingSelected()) { + if (!dataHub.isRecordingSelected()) { menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0, R.string.tracklist_send_to_google); SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0, @@ -825,12 +381,10 @@ public class MyTracksMap extends MapActivity @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (!super.onMenuItemSelected(featureId, item)) { - if (isATrackSelected()) { - MyTracks.getInstance().onActivityResult( - Constants.getActionFromMenuId(item.getItemId()), RESULT_OK, - new Intent()); - return true; - } + MyTracks.getInstance().onActivityResult( + Constants.getActionFromMenuId(item.getItemId()), RESULT_OK, + new Intent()); + return true; } return false; } @@ -858,24 +412,15 @@ public class MyTracksMap extends MapActivity public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case Constants.MENU_MY_LOCATION: { - Location loc = MyTracks.getInstance().getCurrentLocation(); - if (loc != null) { - currentLocation = loc; - setVariation(currentLocation); - mapOverlay.setMyLocation(loc); - mapView.invalidate(); - GeoPoint geoPoint = MyTracksUtils.getGeoPoint(loc); - MapController controller = mapView.getController(); - controller.animateTo(geoPoint); - if (mapView.getZoomLevel() < 18) { - controller.setZoom(18); - } - keepMyLocationVisible = true; + dataHub.forceUpdateLocation(); + keepMyLocationVisible = true; + if (mapView.getZoomLevel() < 18) { + mapView.getController().setZoom(18); } return true; } case Constants.MENU_TOGGLE_LAYERS: { - toggleLayer(); + mapView.setSatellite(!mapView.isSatellite()); return true; } } @@ -885,7 +430,7 @@ public class MyTracksMap extends MapActivity @Override public void onClick(View v) { if (v == messagePane) { - launchMyLocationSettings(); + startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } else if (v == optionsBtn) { optionsBtn.performLongClick(); } @@ -906,191 +451,121 @@ public class MyTracksMap extends MapActivity } @Override - public void onSharedPreferenceChanged( - final SharedPreferences sharedPreferences, final String key) { - Log.d(TAG, - "MyTracksMap.onSharedPreferenceChanged: " + key); - if (key != null) { - uiHandler.post(new Runnable() { - @Override - public void run() { - reloadSharedPreferences(sharedPreferences, key); + public void onProviderStateChange(ProviderState state) { + final int messageId; + final boolean bindClick; + switch (state) { + case DISABLED: + messageId = R.string.status_enable_gps; + bindClick = true; + break; + case NO_FIX: + case BAD_FIX: + messageId = R.string.wait_for_fix; + bindClick = false; + break; + case GOOD_FIX: + // Nothing to show. + messageId = -1; + bindClick = false; + break; + default: + throw new IllegalArgumentException("Unexpected state: " + state); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + if (messageId != -1) { + messageText.setText(messageId); + messagePane.setVisibility(View.VISIBLE); + + if (bindClick) { + messagePane.setOnClickListener(MapActivity.this); + } else { + messagePane.setOnClickListener(null); + } + } else { + messagePane.setVisibility(View.GONE); } - }); - } - } - private final LocationListener locationListener = new LocationListener() { - @Override - public void onProviderEnabled(String provider) { - if (provider.equals(Constants.GPS_PROVIDER)) { - messageText.setText(R.string.wait_for_fix); - } - } - - @Override - public void onProviderDisabled(String provider) { - if (provider.equals(Constants.GPS_PROVIDER)) { - messageText.setText(R.string.status_enable_gps); - messagePane.setVisibility(View.VISIBLE); - messagePane.setOnClickListener(MyTracksMap.this); screen.requestLayout(); } + }); + } + + @Override + public void onCurrentLocationChanged(Location location) { + if (!location.getProvider().equals(Constants.GPS_PROVIDER)) { + Log.d(TAG, + "MapActivity: Network location update received (provider '" + location.getProvider() + "'."); } - @Override - public void onLocationChanged(Location location) { - if (location.getProvider().equals(Constants.GPS_PROVIDER)) { - // Recalculate the variation if there was a jump in location > 1km: - if (currentLocation == null || - location.distanceTo(currentLocation) > 1000) { - setVariation(location); - } - currentLocation = location; - boolean haveGoodFixNow = - currentLocation.getAccuracy() < minRequiredAccuracy; - if (haveGoodFixNow != haveGoodFix) { - haveGoodFix = haveGoodFixNow; - messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE); - screen.requestLayout(); - } - showCurrentLocation(); - } else { - Log.d(TAG, - "MyTracksMap: Network location update received."); + currentLocation = location; + showCurrentLocation(); + } + + @Override + public void onCurrentHeadingChanged(double heading) { + synchronized (this) { + if (mapOverlay.setHeading((float) heading)) { + mapView.postInvalidate(); } } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - if (provider.equals(Constants.GPS_PROVIDER)) { - switch (status) { - case LocationProvider.OUT_OF_SERVICE: - case LocationProvider.TEMPORARILY_UNAVAILABLE: - haveGoodFix = false; - messagePane.setVisibility(View.VISIBLE); - screen.requestLayout(); - break; - } - } - } - }; - - private final SensorEventListener sensorListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent se) { - synchronized (this) { - float magneticHeading = se.values[0]; - double heading = magneticHeading + variation; - if (mapOverlay.setHeading((float) heading)) { - mapView.invalidate(); - } - } - } - - @Override - public void onAccuracyChanged(Sensor s, int accuracy) { - // do nothing - } - }; - - private void reloadSharedPreferences(SharedPreferences sharedPreferences, - String key) { - if (key == null || - key.equals(getString(R.string.min_required_accuracy_key))) { - minRequiredAccuracy = sharedPreferences.getInt( - getString(R.string.min_required_accuracy_key), - MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY); - } - if (key == null || key.equals(getString(R.string.recording_track_key))) { - recordingTrackId = sharedPreferences.getLong( - getString(R.string.recording_track_key), -1); - } - if (key == null || key.equals(getString(R.string.selected_track_key))) { - setSelectedTrack(sharedPreferences.getLong( - getString(R.string.selected_track_key), -1)); - } - - // Show end marker if the track has been selected and is not recording. - // Note: This check must be *after* a call to setSelectedTrack(...) above. - if (isATrackSelected()) { - mapOverlay.setShowEndMarker(!isRecordingSelected()); - mapView.postInvalidate(); - } } - private void readAllNewTrackPoints() { - int numPoints = mapOverlay.getNumLocations(); - if (numPoints >= Constants.MAX_DISPLAYED_TRACK_POINTS) { - // We're about to exceed the maximum allowed number of points, so reload - // the whole track with fewer points (the sampling frequency will be - // lower). - loadSelectedTrack(); - return; + @Override + public void clearWaypoints() { + mapOverlay.clearWaypoints(); + } + + @Override + public void onNewWaypoint(Waypoint waypoint) { + if (LocationUtils.isValidLocation(waypoint.getLocation())) { + // TODO: Optimize locking inside addWaypoint + mapOverlay.addWaypoint(waypoint); } - // Keep a copy of selectedTrackId, because it can change asynchronously. - long currentSelectedTrackId = selectedTrackId; - long lastStoredLocationId = - providerUtils.getLastLocationId(currentSelectedTrackId); - int samplingFrequency = -1; - Location location = new Location(""); - while (currentSelectedTrackId == selectedTrackId) { - Cursor cursor = null; - try { - cursor = providerUtils.getLocationsCursor(currentSelectedTrackId, - lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false); - if (cursor == null || !cursor.moveToFirst()) { - // No (more) data - break; - } + } - final int idColumnIdx = cursor.getColumnIndexOrThrow( - TrackPointsColumns._ID); - do { - long locationId = cursor.getLong(idColumnIdx); - lastSeenLocationId = locationId; - if (firstSeenLocationId == -1) { - // This was our first point, keep its ID - firstSeenLocationId = locationId; - } - if (samplingFrequency == -1) { - // Now we already have at least one point, calculate the sampling - // frequency - long numTotalPoints = lastStoredLocationId - firstSeenLocationId; - samplingFrequency = (int) (1 + numTotalPoints - / Constants.TARGET_DISPLAYED_TRACK_POINTS); - // TODO: This shouldn't happen after adding currentSelectedTrackId, - // but just to be safe until we have 100% confidence. - if (samplingFrequency <= 0) { - Log.w(TAG, - "readAllNewTrackPoints: samplingFreq <= 0, numTotalPoints = " - + numTotalPoints + ", trackId = " + currentSelectedTrackId); - samplingFrequency = 1; - } - } - - providerUtils.fillLocation(cursor, location); - - // Include a point if it fits one of the following criteria: - // - Has the mod for the sampling frequency (includes first point). - // - Is the last point and we are not recording this track. - // - The point is a segment split - if (numPoints % samplingFrequency == 0 || - (!isRecordingSelected() && locationId == lastStoredLocationId) || - !MyTracksUtils.isValidLocation(location)) { - // No need to allocate a new location (we can safely reuse the existing). - mapOverlay.addLocation(location); - } - - numPoints++; - } while (cursor.moveToNext() && - currentSelectedTrackId == selectedTrackId); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } + @Override + public void onNewWaypointsDone() { mapView.postInvalidate(); } + + @Override + public void clearTrackPoints() { + mapOverlay.clearPoints(); + } + + @Override + public void onNewTrackPoint(Location loc) { + mapOverlay.addLocation(loc); + } + + @Override + public void onSegmentSplit() { + mapOverlay.addSegmentSplit(); + } + + @Override + public void onSampledOutTrackPoint(Location loc) { + // We don't care. + } + + @Override + public void onNewTrackPointsDone() { + mapView.postInvalidate(); + } + + @Override + public boolean onUnitsChanged(boolean metric) { + // We don't care. + return false; + } + + @Override + public boolean onReportSpeedChanged(boolean reportSpeed) { + // We don't care. + return false; + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java index 2471001eb..8d0458e34 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java @@ -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); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 6b547836f..9b245915a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -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. + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java index d165f0e1e..d0e37e5e1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java @@ -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); 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 88abfeee6..02bdf6638 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -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); + } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecuter.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecuter.java index ab2cc5f32..984612999 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecuter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/PeriodicTaskExecuter.java @@ -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) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimeSplitTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java similarity index 95% rename from MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimeSplitTask.java rename to MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java index 2b3f52a26..2a50f57fb 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/TimeSplitTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/SplitTask.java @@ -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) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java index 20e7178b2..f709db770 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTask.java @@ -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) { diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java similarity index 97% rename from MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java index 154031126..70b649898 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerFactoryTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactoryTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.services; +package com.google.android.apps.mytracks.services.tasks; import com.google.android.apps.mytracks.services.tasks.PeriodicTask; import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerTaskTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java similarity index 99% rename from MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerTaskTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java index 9b94201f4..5d0bc0c87 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/StatusAnnouncerTaskTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerTaskTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.services; +package com.google.android.apps.mytracks.services.tasks; import static com.google.android.testing.mocking.AndroidMock.capture; import static com.google.android.testing.mocking.AndroidMock.eq;