From 31efcb4a8631136ec67c5b64c7831c7e8b41b986 Mon Sep 17 00:00:00 2001 From: Chihau Chau Date: Thu, 24 Mar 2011 10:34:14 -0300 Subject: [PATCH] Automated merge with http://mytracks.googlecode.com/hg/ --- MyTracks/AndroidManifest.xml | 10 +- .../mytracks/AggregatedStatsActivity.java | 2 +- ...yTracksReceiver.java => BootReceiver.java} | 6 +- .../android/apps/mytracks/ChartActivity.java | 531 +++----- .../android/apps/mytracks/CheckUnits.java | 2 +- .../android/apps/mytracks/Constants.java | 20 + .../apps/mytracks/DeleteAllTracks.java | 3 +- .../apps/mytracks/ExportAllTracks.java | 12 +- .../apps/mytracks/ImportAllTracks.java | 8 +- .../android/apps/mytracks/MapActivity.java | 571 +++++++++ .../{MyTracksOverlay.java => MapOverlay.java} | 40 +- .../android/apps/mytracks/MenuManager.java | 4 +- .../android/apps/mytracks/MyTracks.java | 242 +--- .../android/apps/mytracks/MyTracksMap.java | 1096 ----------------- ...cksSettings.java => SettingsActivity.java} | 26 +- .../android/apps/mytracks/StatsActivity.java | 422 +++---- .../android/apps/mytracks/TrackDataHub.java | 1011 +++++++++++++++ .../apps/mytracks/TrackDataListener.java | 169 +++ .../android/apps/mytracks/TrackList.java | 2 +- .../apps/mytracks/WaypointDetails.java | 2 +- .../android/apps/mytracks/WaypointsList.java | 6 +- .../apps/mytracks/WelcomeActivity.java | 4 +- .../android/apps/mytracks/io/SendToDocs.java | 3 +- .../apps/mytracks/io/SendToFusionTables.java | 14 +- .../apps/mytracks/io/SendToMyMaps.java | 13 +- .../io/backup/ExternalFileBackup.java | 5 +- .../io/backup/MyTracksBackupAgent.java | 5 +- .../io/{ => file}/CsvTrackWriter.java | 4 +- .../mytracks/io/{ => file}/GpxImporter.java | 6 +- .../io/{ => file}/GpxTrackWriter.java | 4 +- .../io/{ => file}/KmlTrackWriter.java | 4 +- .../io/{ => file}/TcxTrackWriter.java | 14 +- .../io/{ => file}/TempFileCleaner.java | 2 +- .../io/{ => file}/TrackFormatWriter.java | 2 +- .../mytracks/io/{ => file}/TrackWriter.java | 6 +- .../io/{ => file}/TrackWriterFactory.java | 2 +- .../mytracks/io/sendtogoogle/SendDialog.java | 6 +- .../services/DefaultTrackNameFactory.java | 4 +- .../mytracks/services/PreferenceManager.java | 13 +- .../services/StatusAnnouncerTask.java | 3 +- .../services/TrackRecordingService.java | 21 +- .../sensors/AntDirectSensorManager.java | 7 +- .../services/sensors/AntSRMSensorManager.java | 5 +- .../sensors/BluetoothSensorManager.java | 3 +- .../sensors/SensorManagerFactory.java | 3 +- .../mytracks/stats/TripStatisticsBuilder.java | 4 +- .../apps/mytracks/util/ChartURLGenerator.java | 4 +- ...{MyTracksUtils.java => LocationUtils.java} | 87 +- .../apps/mytracks/util/StringUtils.java | 4 +- .../apps/mytracks/util/SystemUtils.java | 111 ++ .../mytracks/content/MyTracksLocation.java | 4 +- .../content/MyTracksProviderUtils.java | 25 +- .../content/MyTracksProviderUtilsImpl.java | 2 +- ...ksOverlayTest.java => MapOverlayTest.java} | 6 +- .../io/{ => file}/CsvTrackWriterTest.java | 2 +- .../io/{ => file}/GpxImporterTest.java | 3 +- .../io/{ => file}/GpxTrackWriterTest.java | 5 +- .../io/{ => file}/KmlTrackWriterTest.java | 3 +- .../io/{ => file}/TcxTrackWriterTest.java | 4 +- .../io/{ => file}/TempFileCleanerTest.java | 4 +- .../io/{ => file}/TrackFormatWriterTest.java | 3 +- .../io/{ => file}/TrackWriterTest.java | 4 +- .../services/TrackRecordingServiceTest.java | 4 +- .../sensors/AntDirectSensorManagerTest.java | 4 +- .../sensors/SensorManagerFactoryTest.java | 4 +- 65 files changed, 2445 insertions(+), 2180 deletions(-) rename MyTracks/src/com/google/android/apps/mytracks/{MyTracksReceiver.java => BootReceiver.java} (91%) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/MapActivity.java rename MyTracks/src/com/google/android/apps/mytracks/{MyTracksOverlay.java => MapOverlay.java} (94%) delete mode 100644 MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java rename MyTracks/src/com/google/android/apps/mytracks/{MyTracksSettings.java => SettingsActivity.java} (93%) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/TrackDataListener.java rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/CsvTrackWriter.java (97%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/GpxImporter.java (99%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/GpxTrackWriter.java (97%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/KmlTrackWriter.java (98%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/TcxTrackWriter.java (94%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/TempFileCleaner.java (97%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/TrackFormatWriter.java (98%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/TrackWriter.java (98%) rename MyTracks/src/com/google/android/apps/mytracks/io/{ => file}/TrackWriterFactory.java (98%) rename MyTracks/src/com/google/android/apps/mytracks/util/{MyTracksUtils.java => LocationUtils.java} (74%) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/util/SystemUtils.java rename MyTracksTest/src/com/google/android/apps/mytracks/{MyTracksOverlayTest.java => MapOverlayTest.java} (97%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/CsvTrackWriterTest.java (79%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/GpxImporterTest.java (98%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/GpxTrackWriterTest.java (93%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/KmlTrackWriterTest.java (97%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/TcxTrackWriterTest.java (94%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/TempFileCleanerTest.java (96%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/TrackFormatWriterTest.java (98%) rename MyTracksTest/src/com/google/android/apps/mytracks/io/{ => file}/TrackWriterTest.java (98%) diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 2a1bbd2d7..40c852901 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -17,8 +17,8 @@ limitations under the License. + android:versionCode="27" + android:versionName="1.1.4"> @@ -52,7 +52,7 @@ limitations under the License. - @@ -73,7 +73,7 @@ limitations under the License. - - + diff --git a/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java index 7030b30db..cb385f35c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/AggregatedStatsActivity.java @@ -67,7 +67,7 @@ public class AggregatedStatsActivity extends Activity implements sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET); SharedPreferences preferences = getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); if (preferences != null) { metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true); preferences.registerOnSharedPreferenceChangeListener(this); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksReceiver.java b/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java similarity index 91% rename from MyTracks/src/com/google/android/apps/mytracks/MyTracksReceiver.java rename to MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java index 040e872e6..3ed021fb8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksReceiver.java +++ b/MyTracks/src/com/google/android/apps/mytracks/BootReceiver.java @@ -41,17 +41,17 @@ import android.util.Log; * * @author Bartlomiej Niechwiej */ -public class MyTracksReceiver extends BroadcastReceiver { +public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.d(TAG, "MyTracksReceiver.onReceive: " + intent.getAction()); + Log.d(TAG, "BootReceiver.onReceive: " + intent.getAction()); if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) { Intent startIntent = new Intent(context, TrackRecordingService.class); startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true); context.startService(startIntent); } else { - Log.w(TAG, "MyTracksReceiver: unsupported action"); + Log.w(TAG, "BootReceiver: unsupported action"); } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java index 1deb8a773..d62b1e2dc 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java @@ -19,29 +19,21 @@ import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.apps.mytracks.ChartView.Mode; import com.google.android.apps.mytracks.content.MyTracksLocation; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Sensor; -import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.TrackPointsColumns; -import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; import com.google.android.apps.mytracks.util.ApiFeatures; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.maps.mytracks.R; import android.app.Activity; -import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.database.Cursor; import android.location.Location; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -58,48 +50,28 @@ import java.util.ArrayList; * An activity that displays a chart from the track point provider. * * @author Sandor Dornbush + * @author Rodrigo Damazio */ -public class ChartActivity extends Activity implements - SharedPreferences.OnSharedPreferenceChangeListener { - - private final static int BUFFER_SIZE = 1024; - private double profileLength = 0; - - private boolean metricUnits = true; - private boolean reportSpeed = true; - - /** - * The track id that is displayed. - */ - private long selectedTrackId = -1; - - /** - * Id of the last location that was seen when reading tracks from the - * provider. This is used to determine which locations are new compared to the - * last time the chart was updated. - */ - private long lastSeenLocationId = -1; - - private long startTime = -1; - - private Location lastLocation; - - /** - * The id of the track currently being recorded. - */ - private long recordingTrackId = -1; +public class ChartActivity extends Activity implements TrackDataListener { private final DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR); private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR); + private final ArrayList 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/CheckUnits.java b/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java index d1fc56029..556ad8f7d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java +++ b/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java @@ -78,7 +78,7 @@ class CheckUnits { private static void accept(Context context, SharedPreferences preferences) { recordCheckPerformed(preferences); - Intent startIntent = new Intent(context, MyTracksSettings.class); + Intent startIntent = new Intent(context, SettingsActivity.class); context.startActivity(startIntent); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/Constants.java b/MyTracks/src/com/google/android/apps/mytracks/Constants.java index ca4d93b9c..46a9db348 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/Constants.java +++ b/MyTracks/src/com/google/android/apps/mytracks/Constants.java @@ -140,6 +140,12 @@ public abstract class Constants { */ public static final int MAX_LOADED_TRACK_POINTS = 20000; + /** + * Maximum number of track points ever loaded at once from the provider into + * memory in a single call to read points. + */ + public static final int MAX_LOADED_TRACK_POINTS_PER_BATCH = 1000; + /** * Maximum number of way points displayed by the map overlay. */ @@ -216,6 +222,20 @@ public abstract class Constants { public static final String MAPSHOP_BASE_URL = "http://maps.google.com/maps/ms"; + /* + * Default values - keep in sync with those in preferences.xml. + */ + + public static final int DEFAULT_ANNOUNCEMENT_FREQUENCY = -1; + public static final int DEFAULT_AUTO_RESUME_TRACK_TIMEOUT = 10; // In min. + public static final int DEFAULT_MAX_RECORDING_DISTANCE = 200; + public static final int DEFAULT_MIN_RECORDING_DISTANCE = 5; + public static final int DEFAULT_MIN_RECORDING_INTERVAL = 0; + public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200; + public static final int DEFAULT_SPLIT_FREQUENCY = 0; + + public static final String SETTINGS_NAME = "SettingsActivity"; + /** * This is an abstract utility class. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java index df54f04eb..13c1598ed 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java @@ -61,8 +61,9 @@ public class DeleteAllTracks extends Handler { Log.w(Constants.TAG, "deleting all!"); MyTracksProviderUtils.Factory.get(context).deleteAllTracks(); SharedPreferences prefs = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); SharedPreferences.Editor editor = prefs.edit(); + // TODO: Go through data manager editor.putLong(context.getString(R.string.selected_track_key), -1); ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor); if (done != null) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java index 349c1cd67..93f2e9aa5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java @@ -17,10 +17,10 @@ package com.google.android.apps.mytracks; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.TracksColumns; -import com.google.android.apps.mytracks.io.TrackWriter; -import com.google.android.apps.mytracks.io.TrackWriterFactory; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.io.file.TrackWriter; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -113,14 +113,14 @@ public class ExportAllTracks { */ private void aquireLocksAndExport() { SharedPreferences prefs = - activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + activity.getSharedPreferences(Constants.SETTINGS_NAME, 0); long recordingTrackId = -1; if (prefs != null) { recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1); } if (recordingTrackId != -1) { - wakeLock = MyTracksUtils.acquireWakeLock(activity, wakeLock); + wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock); } // Now we can safely export everything. diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java index 64d5799bb..8826e40c7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java @@ -16,9 +16,9 @@ package com.google.android.apps.mytracks; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.io.GpxImporter; +import com.google.android.apps.mytracks.io.file.GpxImporter; import com.google.android.apps.mytracks.util.FileUtils; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -82,13 +82,13 @@ public class ImportAllTracks { * track. Acquire a wake lock if there is no current track. */ private void aquireLocksAndImport() { - SharedPreferences prefs = activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + SharedPreferences prefs = activity.getSharedPreferences(Constants.SETTINGS_NAME, 0); long recordingTrackId = -1; if (prefs != null) { recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1); } if (recordingTrackId != -1) { - wakeLock = MyTracksUtils.acquireWakeLock(activity, wakeLock); + wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock); } // Now we can safely import everything. diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java new file mode 100644 index 000000000..e3430bc80 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java @@ -0,0 +1,571 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +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.Track; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.services.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.LocationUtils; +import com.google.android.maps.GeoPoint; +import com.google.android.maps.MapController; +import com.google.android.maps.MapView; +import com.google.android.maps.mytracks.R; + +import android.content.Intent; +import android.location.Location; +import android.os.Bundle; +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.View.OnCreateContextMenuListener; +import android.view.Window; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +/** + * The map view activity of the MyTracks application. + * + * @author Leif Hendrik Wilden + * @author Rodrigo Damazio + */ +public class MapActivity extends com.google.android.maps.MapActivity + implements View.OnTouchListener, View.OnClickListener, + TrackDataListener { + + // Saved instance state keys: + // --------------------------- + + private static final String KEY_CURRENT_LOCATION = "currentLocation"; + private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible"; + + private TrackDataHub dataHub; + + /** + * True if the map should be scrolled so that the pointer is always in the + * visible area. + */ + private boolean keepMyLocationVisible; + + /** + * The current pointer location. + * This is kept to quickly center on it when the user requests. + */ + private Location currentLocation; + + // UI elements: + // ------------- + + private RelativeLayout screen; + private MapView mapView; + private MapOverlay mapOverlay; + private LinearLayout messagePane; + private TextView messageText; + private LinearLayout busyPane; + private ImageButton optionsBtn; + + private MenuItem myLocation; + private MenuItem toggleLayers; + + /** + * 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 + * return false here and still comply with the terms of service. + */ + @Override + protected boolean isRouteDisplayed() { + return false; + } + + /** + * We are displaying a location. This needs to return true in order to comply + * with the terms of service. + */ + @Override + protected boolean isLocationDisplayed() { + return true; + } + + // Application life cycle: + // ------------------------ + + @Override + public void onCreate(Bundle bundle) { + Log.d(TAG, "MapActivity.onCreate"); + super.onCreate(bundle); + + // The volume we want to control is the Text-To-Speech volume + int volumeStream = + new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); + setVolumeControlStream(volumeStream); + + dataHub = MyTracks.getInstance().getDataHub(); + + // We don't need a window title bar: + requestWindowFeature(Window.FEATURE_NO_TITLE); + + // Inflate the layout: + setContentView(R.layout.mytracks_layout); + + // Remove the window's background because the MapView will obscure it + getWindow().setBackgroundDrawable(null); + + // Set up a map overlay: + screen = (RelativeLayout) findViewById(R.id.screen); + mapView = (MapView) findViewById(R.id.map); + mapView.requestFocus(); + 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); + optionsBtn = (ImageButton) findViewById(R.id.showOptions); + + optionsBtn.setOnCreateContextMenuListener(contextMenuListener); + optionsBtn.setOnClickListener(this); + } + + @Override + protected void onRestoreInstanceState(Bundle bundle) { + Log.d(TAG, "MapActivity.onRestoreInstanceState"); + if (bundle != null) { + super.onRestoreInstanceState(bundle); + 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) { + showCurrentLocation(); + } + } else { + currentLocation = null; + } + } + } + + @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: + // ------------------- + + /** + * Shows the options button if a track is selected, or hide it if not. + */ + private void updateOptionsButton(boolean trackSelected) { + optionsBtn.setVisibility( + trackSelected ? View.VISIBLE : View.INVISIBLE); + } + + /** + * Tests if a location is visible. + * + * @param location a given location + * @return true if the given location is within the visible map area + */ + private boolean locationIsVisible(Location location) { + if (location == null || mapView == null) { + return false; + } + GeoPoint center = mapView.getMapCenter(); + int latSpan = mapView.getLatitudeSpan(); + int lonSpan = mapView.getLongitudeSpan(); + + // Bottom of map view is obscured by zoom controls/buttons. + // Subtract a margin from the visible area: + GeoPoint marginBottom = mapView.getProjection().fromPixels( + 0, mapView.getHeight()); + GeoPoint marginTop = mapView.getProjection().fromPixels(0, + mapView.getHeight() + - mapView.getZoomButtonsController().getZoomControls().getHeight()); + int margin = + Math.abs(marginTop.getLatitudeE6() - marginBottom.getLatitudeE6()); + GeoRect r = new GeoRect(center, latSpan, lonSpan); + r.top += margin; + + GeoPoint geoPoint = LocationUtils.getGeoPoint(location); + return r.contains(geoPoint); + } + + /** + * Moves the location pointer to the current location and center the map if + * the current location is outside the visible area. + */ + private void showCurrentLocation() { + if (currentLocation == null || mapOverlay == null || mapView == null) { + return; + } + mapOverlay.setMyLocation(currentLocation); + mapView.postInvalidate(); + if (keepMyLocationVisible && !locationIsVisible(currentLocation)) { + GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation); + MapController controller = mapView.getController(); + 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 track the track + */ + private void zoomMapToBoundaries(Track track) { + if (mapView == null) { + return; + } + + if (track == null || track.getNumberOfPoints() < 2) { + return; + } + + TripStatistics stats = track.getStatistics(); + int bottom = stats.getBottom(); + int left = stats.getLeft(); + int latSpanE6 = stats.getTop() - bottom; + int lonSpanE6 = stats.getRight() - left; + if (latSpanE6 > 0 + && latSpanE6 < 180E6 + && lonSpanE6 > 0 + && lonSpanE6 < 360E6) { + keepMyLocationVisible = false; + GeoPoint center = new GeoPoint( + bottom + latSpanE6 / 2, + left + lonSpanE6 / 2); + if (LocationUtils.isValidGeoPoint(center)) { + mapView.getController().setCenter(center); + mapView.getController().zoomToSpan(latSpanE6, lonSpanE6); + } + } + } + + /** + * 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; + GeoPoint center = new GeoPoint( + (int) (wpt.getLocation().getLatitude() * 1E6), + (int) (wpt.getLocation().getLongitude() * 1E6)); + mapView.getController().setCenter(center); + mapView.getController().setZoom(20); + mapView.invalidate(); + } + } + + @Override + public void onSelectedTrackChanged(final Track track, final boolean isRecording) { + runOnUiThread(new Runnable() { + @Override + public void run() { + boolean trackSelected = track != null; + updateOptionsButton(trackSelected); + + mapOverlay.setTrackDrawingEnabled(trackSelected); + + if (trackSelected) { + busyPane.setVisibility(View.VISIBLE); + + zoomMapToBoundaries(track); + + mapOverlay.setShowEndMarker(!isRecording); + busyPane.setVisibility(View.GONE); + } + mapView.invalidate(); + } + }); + } + + private final OnCreateContextMenuListener contextMenuListener = + new OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + menu.setHeaderTitle(R.string.tracklist_this_track); + menu.add(0, Constants.MENU_EDIT, 0, + R.string.tracklist_edit_track); + 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, + R.string.tracklist_share_track); + share.add(0, Constants.MENU_SHARE_LINK, 0, + R.string.tracklist_share_link); + share.add(0, Constants.MENU_SHARE_GPX_FILE, 0, + R.string.tracklist_share_gpx_file); + share.add(0, Constants.MENU_SHARE_KML_FILE, 0, + R.string.tracklist_share_kml_file); + share.add(0, Constants.MENU_SHARE_CSV_FILE, 0, + R.string.tracklist_share_csv_file); + share.add(0, Constants.MENU_SHARE_TCX_FILE, 0, + R.string.tracklist_share_tcx_file); + SubMenu save = menu.addSubMenu(0, + Constants.MENU_WRITE_TO_SD_CARD, 0, + R.string.tracklist_write_to_sd); + save.add(0, Constants.MENU_SAVE_GPX_FILE, 0, + R.string.tracklist_save_as_gpx); + save.add(0, Constants.MENU_SAVE_KML_FILE, 0, + R.string.tracklist_save_as_kml); + save.add(0, Constants.MENU_SAVE_CSV_FILE, 0, + R.string.tracklist_save_as_csv); + save.add(0, Constants.MENU_SAVE_TCX_FILE, 0, + R.string.tracklist_save_as_tcx); + menu.add(0, Constants.MENU_CLEAR_MAP, 0, + R.string.tracklist_clear_map); + menu.add(0, Constants.MENU_DELETE, 0, + R.string.tracklist_delete_track); + } + } + }; + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (!super.onMenuItemSelected(featureId, item)) { + MyTracks.getInstance().onActivityResult( + Constants.getActionFromMenuId(item.getItemId()), RESULT_OK, + new Intent()); + return true; + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + myLocation = menu.add(0, Constants.MENU_MY_LOCATION, 0, + R.string.mylocation); + myLocation.setIcon(android.R.drawable.ic_menu_mylocation); + toggleLayers = menu.add(0, Constants.MENU_TOGGLE_LAYERS, 0, + R.string.switch_to_sat); + toggleLayers.setIcon(android.R.drawable.ic_menu_mapmode); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + toggleLayers.setTitle(mapView.isSatellite() ? + R.string.switch_to_map : R.string.switch_to_sat); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Constants.MENU_MY_LOCATION: { + dataHub.forceUpdateLocation(); + keepMyLocationVisible = true; + if (mapView.getZoomLevel() < 18) { + mapView.getController().setZoom(18); + } + return true; + } + case Constants.MENU_TOGGLE_LAYERS: { + mapView.setSatellite(!mapView.isSatellite()); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View v) { + if (v == messagePane) { + startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } else if (v == optionsBtn) { + optionsBtn.performLongClick(); + } + } + + /** + * We want the pointer to become visible again in case of the next location + * update: + */ + @Override + public boolean onTouch(View view, MotionEvent event) { + if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) { + if (!locationIsVisible(currentLocation)) { + keepMyLocationVisible = false; + } + } + return false; + } + + @Override + 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); + } + + 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() + "'."); + } + + currentLocation = location; + showCurrentLocation(); + } + + @Override + public void onCurrentHeadingChanged(double heading) { + synchronized (this) { + if (mapOverlay.setHeading((float) heading)) { + mapView.postInvalidate(); + } + } + } + + @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); + } + } + + @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/MyTracksOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java similarity index 94% rename from MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java rename to MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java index 217d86081..c72abc5d2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks; import com.google.android.apps.mytracks.content.Waypoint; -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.MapView; import com.google.android.maps.Overlay; @@ -45,7 +45,7 @@ import java.util.concurrent.BlockingQueue; * * @author Leif Hendrik Wilden */ -public class MyTracksOverlay extends Overlay { +public class MapOverlay extends Overlay { private final Drawable[] arrows; private final int arrowWidth, arrowHeight; @@ -79,14 +79,25 @@ public class MyTracksOverlay extends Overlay { private static class CachedLocation { public final boolean valid; public final GeoPoint geoPoint; - + + /** + * Constructor for an invalid cached location. + */ + public CachedLocation() { + this.valid = false; + this.geoPoint = null; + } + + /** + * Constructor for a potentially valid cached location. + */ public CachedLocation(Location location) { - this.valid = MyTracksUtils.isValidLocation(location); - this.geoPoint = valid ? MyTracksUtils.getGeoPoint(location) : null; + this.valid = LocationUtils.isValidLocation(location); + this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null; } }; - - public MyTracksOverlay(Context context) { + + public MapOverlay(Context context) { this.context = context; this.waypoints = new ArrayList(); @@ -164,6 +175,13 @@ public class MyTracksOverlay extends Overlay { pendingPoints.offer(new CachedLocation(l)); } + /** + * Adds a segment split to the map overlay. + */ + public void addSegmentSplit() { + pendingPoints.offer(new CachedLocation()); + } + public void addWaypoint(Waypoint wpt) { // Note: We don't cache waypoints, because it's not worth the effort. if (wpt != null && wpt.getLocation() != null) { @@ -172,7 +190,7 @@ public class MyTracksOverlay extends Overlay { } } } - + public int getNumLocations() { synchronized (points) { return points.size() + pendingPoints.size(); @@ -260,7 +278,7 @@ public class MyTracksOverlay extends Overlay { synchronized (waypoints) {; for (Waypoint wpt : waypoints) { Location loc = wpt.getLocation(); - drawElement(canvas, projection, MyTracksUtils.getGeoPoint(loc), + drawElement(canvas, projection, LocationUtils.getGeoPoint(loc), wpt.getType() == Waypoint.TYPE_STATISTICS ? statsMarker : waypointMarker, -(markerWidth / 2) + 3, -markerHeight); } @@ -274,7 +292,7 @@ public class MyTracksOverlay extends Overlay { } Point pt = drawElement(canvas, projection, - MyTracksUtils.getGeoPoint(myLocation), arrows[lastHeading], + LocationUtils.getGeoPoint(myLocation), arrows[lastHeading], -(arrowWidth / 2) + 3, -(arrowHeight / 2)); // Draw the error circle. float radius = projection.metersToEquatorPixels(myLocation.getAccuracy()); @@ -423,7 +441,7 @@ public class MyTracksOverlay extends Overlay { return false; } - final Location tapLocation = MyTracksUtils.getLocation(p); + final Location tapLocation = LocationUtils.getLocation(p); double dmin = Double.MAX_VALUE; Waypoint waypoint = null; synchronized (waypoints) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java index 289a0aa57..97ef2adae 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java @@ -78,7 +78,7 @@ class MenuManager { return startActivity(SensorStateActivity.class); } case R.id.menu_settings: { - return startActivity(MyTracksSettings.class); + return startActivity(SettingsActivity.class); } case R.id.menu_aggregated_stats: { return startActivity(AggregatedStatsActivity.class); @@ -87,7 +87,7 @@ class MenuManager { return startActivity(WelcomeActivity.class); } case Constants.MENU_CLEAR_MAP: { - activity.setSelectedTrackId(-1); + activity.clearSelectedTrack(); return true; } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 486fa5022..cfa4c85ef 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -27,15 +27,15 @@ import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.io.AuthManager; import com.google.android.apps.mytracks.io.AuthManagerFactory; -import com.google.android.apps.mytracks.io.GpxImporter; import com.google.android.apps.mytracks.io.SendToDocs; import com.google.android.apps.mytracks.io.SendToFusionTables; import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener; import com.google.android.apps.mytracks.io.SendToMyMaps; -import com.google.android.apps.mytracks.io.TempFileCleaner; -import com.google.android.apps.mytracks.io.TrackWriter; -import com.google.android.apps.mytracks.io.TrackWriterFactory; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.io.file.GpxImporter; +import com.google.android.apps.mytracks.io.file.TempFileCleaner; +import com.google.android.apps.mytracks.io.file.TrackWriter; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.io.mymaps.MapsFacade; import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants; import com.google.android.apps.mytracks.io.sendtogoogle.ResultDialogFactory; @@ -47,7 +47,7 @@ import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.TrackRecordingService; import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.FileUtils; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -55,15 +55,11 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.TabActivity; import android.content.ComponentName; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Resources; -import android.location.Location; -import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -101,12 +97,13 @@ import org.xml.sax.SAXException; * @author Leif Hendrik Wilden */ public class MyTracks extends TabActivity implements OnTouchListener, - OnSharedPreferenceChangeListener, ProgressIndicator { + ProgressIndicator { /** * Singleton instance */ private static MyTracks instance; + private TrackDataHub dataHub; private ChartActivity chartActivity; /* @@ -151,18 +148,8 @@ public class MyTracks extends TabActivity implements OnTouchListener, private ITrackRecordingService trackRecordingService; /** - * The id of the currently recording track. + * Does the user want to share the current track. */ - private long recordingTrackId = -1; - - /** - * The id of the currently selected track. - */ - private long selectedTrackId = -1; - - /** - * Does the user want to share the current track. - */ private boolean shareRequested = false; /** @@ -234,7 +221,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, public boolean isRecording() { if (trackRecordingService == null) { // Fall back to alternative check method. - return isRecordingBasedOnSharedPreferences(); + return dataHub.isRecording(); } try { return trackRecordingService.isRecording(); @@ -244,16 +231,10 @@ public class MyTracks extends TabActivity implements OnTouchListener, Log.e(TAG, "MyTracks: Remote exception.", e); // Fall back to alternative check method. - return isRecordingBasedOnSharedPreferences(); + return dataHub.isRecording(); } } - private boolean isRecordingBasedOnSharedPreferences() { - // TrackRecordingService guarantees that recordingTrackId is set to - // -1 if the track has been stopped. - return recordingTrackId >= 0; - } - /* * Application lifetime events: * ============================ @@ -265,13 +246,14 @@ public class MyTracks extends TabActivity implements OnTouchListener, super.onCreate(savedInstanceState); instance = this; ApiFeatures apiFeatures = ApiFeatures.getInstance(); - if (!MyTracksUtils.isRelease(this)) { + if (!SystemUtils.isRelease(this)) { apiFeatures.getApiPlatformAdapter().enableStrictMode(); } providerUtils = MyTracksProviderUtils.Factory.get(this); + dataHub = new TrackDataHub(this, providerUtils); menuManager = new MenuManager(this); - sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0); dialogManager = new DialogManager(this); // The volume we want to control is the Text-To-Speech volume @@ -287,7 +269,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, tabHost.addTab(tabHost.newTabSpec("tab1") .setIndicator("Map", res.getDrawable( android.R.drawable.ic_menu_mapmode)) - .setContent(new Intent(this, MyTracksMap.class))); + .setContent(new Intent(this, MapActivity.class))); tabHost.addTab(tabHost.newTabSpec("tab2") .setIndicator("Stats", res.getDrawable(R.drawable.menu_stats)) .setContent(new Intent(this, StatsActivity.class))); @@ -312,20 +294,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, tabHost.addView(layout); layout.setOnTouchListener(this); - if (sharedPreferences != null) { - selectedTrackId = - sharedPreferences.getLong(getString(R.string.selected_track_key), -1); - recordingTrackId = sharedPreferences.getLong( - getString(R.string.recording_track_key), -1); - sharedPreferences.registerOnSharedPreferenceChangeListener(this); - Log.d(TAG, "recordingTrackId: " + recordingTrackId - + ", selectedTrackId: " + selectedTrackId); - if (recordingTrackId > 0) { - Intent startIntent = new Intent(this, TrackRecordingService.class); - startService(startIntent); - } - } - // This will show the eula until the user accepts or quits the app. Eula.showEulaRequireAcceptance(this); @@ -353,10 +321,24 @@ public class MyTracks extends TabActivity implements OnTouchListener, @Override protected void onDestroy() { Log.d(TAG, "MyTracks.onDestroy"); + + dataHub.destroy(); + tryUnbindTrackRecordingService(); super.onDestroy(); } + @Override + protected void onStop() { + Log.d(TAG, "MyTracks.onStop"); + + dataHub.stop(); + + // Clean up any temporary track files. + TempFileCleaner.clean(); + super.onStop(); + } + @Override protected void onPause() { // Called when activity is going into the background, but has not (yet) been @@ -376,11 +358,10 @@ public class MyTracks extends TabActivity implements OnTouchListener, } @Override - protected void onStop() { - Log.d(TAG, "MyTracks.onStop"); - // Clean up any temporary track files. - TempFileCleaner.clean(); - super.onStop(); + protected void onStart() { + Log.d(TAG, "MyTracks.onStart"); + super.onStart(); + dataHub.start(); } /* @@ -397,7 +378,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, @Override public boolean onPrepareOptionsMenu(Menu menu) { menuManager.onPrepareOptionsMenu(menu, providerUtils.getLastTrack() != null, - isRecording(), selectedTrackId >= 0); + isRecording(), dataHub.isATrackSelected()); return super.onPrepareOptionsMenu(menu); } @@ -455,6 +436,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, public void onActivityResult(int requestCode, int resultCode, final Intent results) { TrackFileFormat exportFormat = null; + final long trackId = results.getLongExtra("trackid", dataHub.getSelectedTrackId()); switch (requestCode) { case Constants.GET_LOGIN: { if (resultCode != RESULT_OK || auth == null || !auth.authResult(resultCode, results)) { @@ -464,9 +446,9 @@ public class MyTracks extends TabActivity implements OnTouchListener, } case Constants.SHOW_TRACK: { if (results != null) { - final long trackId = results.getLongExtra("trackid", -1); if (trackId >= 0) { - setSelectedTrackId(trackId); + dataHub.loadTrack(trackId); + // The track list passed the requested action as result code. Hand // it off to the onAcitivtyResult for further processing: if (resultCode != Constants.SHOW_TRACK) { @@ -480,8 +462,8 @@ public class MyTracks extends TabActivity implements OnTouchListener, if (results != null) { final long waypointId = results.getLongExtra("waypointid", -1); if (waypointId >= 0) { - MyTracksMap map = - (MyTracksMap) getLocalActivityManager().getActivity("tab1"); + MapActivity map = + (MapActivity) getLocalActivityManager().getActivity("tab1"); if (map != null) { getTabHost().setCurrentTab(0); map.showWaypoint(waypointId); @@ -492,14 +474,12 @@ public class MyTracks extends TabActivity implements OnTouchListener, } case Constants.DELETE_TRACK: { if (results != null && resultCode == RESULT_OK) { - final long trackId = results.getLongExtra("trackid", selectedTrackId); deleteTrack(trackId); } break; } case Constants.EDIT_DETAILS: { if (results != null && resultCode == RESULT_OK) { - final long trackId = results.getLongExtra("trackid", selectedTrackId); Intent intent = new Intent(this, TrackDetails.class); intent.putExtra("trackid", trackId); startActivity(intent); @@ -514,7 +494,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, case Constants.GET_MAP: { // User picked a map to upload to if (resultCode == RESULT_OK) { - results.putExtra("trackid", selectedTrackId); + results.putExtra("trackid", dataHub.getSelectedTrackId()); if (results.hasExtra("mapid")) { sendToMyMapsMapId = results.getStringExtra("mapid"); } @@ -528,17 +508,11 @@ public class MyTracks extends TabActivity implements OnTouchListener, // Authenticated with Google My Maps if (results != null && resultCode == RESULT_OK) { final String mapId; - final long trackId; if (results.hasExtra("mapid")) { mapId = results.getStringExtra("mapid"); } else { mapId = "new"; } - if (results.hasExtra("trackid")) { - trackId = results.getLongExtra("trackid", -1); - } else { - trackId = selectedTrackId; - } sendToGoogleMaps(trackId, mapId); } else { @@ -549,13 +523,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, case Constants.AUTHENTICATE_TO_FUSION_TABLES: { // Authenticated with Google Fusion Tables if (results != null && resultCode == RESULT_OK) { - final long trackId; - if (results.hasExtra("trackid")) { - trackId = results.getLongExtra("trackid", -1); - } else { - trackId = selectedTrackId; - } - sendToFusionTables(trackId); } else { onSendToGoogleDone(); @@ -574,7 +541,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, case Constants.AUTHENTICATE_TO_TRIX: { // Authenticated with Trix if (resultCode == RESULT_OK) { - final long trackId = results.getLongExtra("trackid", selectedTrackId); sendToGoogleDocs(trackId); } else { onSendToGoogleDone(); @@ -594,19 +560,18 @@ public class MyTracks extends TabActivity implements OnTouchListener, if (exportFormat == null) { exportFormat = TrackFileFormat.TCX; } if (results != null && resultCode == Activity.RESULT_OK) { - final long trackId = results.getLongExtra("trackid", selectedTrackId); if (trackId >= 0) { saveTrack(trackId, exportFormat); } } break; case Constants.SHARE_LINK: { - Track selectedTrack = providerUtils.getTrack(selectedTrackId); + Track selectedTrack = providerUtils.getTrack(dataHub.getSelectedTrackId()); if (selectedTrack != null) { if (!TextUtils.isEmpty(selectedTrack.getMapId())) { shareLinkToMap(MapsFacade.buildMapUrl(selectedTrack.getMapId())); } else if (!TextUtils.isEmpty(selectedTrack.getTableId())) { - shareLinkToMap(getFusionTablesUrl(selectedTrackId)); + shareLinkToMap(getFusionTablesUrl(dataHub.getSelectedTrackId())); } else { shareRequested = true; dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE); @@ -627,7 +592,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, if (exportFormat == null) { exportFormat = TrackFileFormat.TCX; } if (results != null && resultCode == Activity.RESULT_OK) { - final long trackId = results.getLongExtra("trackid", selectedTrackId); if (trackId >= 0) { sendTrack(trackId, exportFormat); } @@ -635,7 +599,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, break; } case Constants.CLEAR_MAP: { - setSelectedTrackId(-1); + dataHub.unloadCurrentTrack(); break; } case Constants.WELCOME: { @@ -657,19 +621,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, return false; } - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - if (key != null && key.equals(getString(R.string.selected_track_key))) { - selectedTrackId = sharedPreferences.getLong( - getString(R.string.selected_track_key), -1); - } - if (key != null && key.equals(getString(R.string.recording_track_key))) { - recordingTrackId = sharedPreferences.getLong( - getString(R.string.recording_track_key), -1); - } - } - /** * Resets status information for sending to MyMaps/Docs. */ @@ -715,7 +666,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, } if (trackIdsImported != null && trackIdsImported.length > 0) { // select last track from import file - setSelectedTrackId(trackIdsImported[trackIdsImported.length - 1]); + dataHub.loadTrack(trackIdsImported[trackIdsImported.length - 1]); } else { dialogManager.showMessageDialog(message, false/* success */); } @@ -787,8 +738,8 @@ public class MyTracks extends TabActivity implements OnTouchListener, public void onClick(DialogInterface dialog, int i) { dialog.dismiss(); providerUtils.deleteTrack(trackId); - if (trackId == selectedTrackId) { - setSelectedTrackId(-1); + if (trackId == dataHub.getSelectedTrackId()) { + dataHub.unloadCurrentTrack(); } }}); builder.setNegativeButton(getString(R.string.no), @@ -801,46 +752,6 @@ public class MyTracks extends TabActivity implements OnTouchListener, dialog.show(); } - public Location getCurrentLocation() { - // TODO: Let's look at more advanced algorithms to determine the best - // current location. - LocationManager locationManager = - (LocationManager) getSystemService(Context.LOCATION_SERVICE); - if (locationManager == null) { - return null; - } - final long maxAgeMilliSeconds = 1000 * 60 * 1; // 1 minute - final long maxAgeNetworkMilliSeconds = 1000 * 60 * 10; // 10 minutes - final long now = System.currentTimeMillis(); - Location loc = locationManager.getLastKnownLocation( - Constants.GPS_PROVIDER); - if (loc == null || loc.getTime() < now - maxAgeMilliSeconds) { - // We don't have a recent GPS fix, just use cell towers if available - loc = locationManager.getLastKnownLocation( - LocationManager.NETWORK_PROVIDER); - if (loc == null || loc.getTime() < now - maxAgeNetworkMilliSeconds) { - // We don't have a recent cell tower location, let the user know: - Toast.makeText(this, getString(R.string.status_no_location), - Toast.LENGTH_LONG).show(); - return null; - } else { - // Let the user know we have only an approximate location: - Toast.makeText(this, getString(R.string.status_approximate_location), - Toast.LENGTH_LONG).show(); - } - } - return loc; - } - - public Location getLastLocation() { - if (providerUtils.getLastLocationId(recordingTrackId) < 0) { - return null; - } - LocationManager locationManager = - (LocationManager) getSystemService(Context.LOCATION_SERVICE); - return locationManager.getLastKnownLocation(Constants.GPS_PROVIDER); - } - /** * Inserts a waypoint marker. * @@ -917,9 +828,9 @@ public class MyTracks extends TabActivity implements OnTouchListener, private void startRecordingNewTrack( ITrackRecordingService trackRecordingService) { try { - recordingTrackId = trackRecordingService.startNewTrack(); + long recordingTrackId = trackRecordingService.startNewTrack(); // Select the recording track. - setSelectedTrackId(recordingTrackId); + dataHub.loadTrack(recordingTrackId); Toast.makeText(this, getString(R.string.status_now_recording), Toast.LENGTH_SHORT).show(); // TODO: We catch Exception, because after eliminating the service process @@ -954,7 +865,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, public void stopRecording() { if (trackRecordingService != null) { // Save the track id as the shared preference will overwrite the recording track id. - long currentTrackId = recordingTrackId; + long currentTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1); try { trackRecordingService.endCurrentTrack(); // TODO: We catch Exception, because after eliminating the service process @@ -962,10 +873,13 @@ public class MyTracks extends TabActivity implements OnTouchListener, } catch (Exception e) { Log.e(TAG, "Unable to stop recording.", e); } - Intent intent = new Intent(MyTracks.this, TrackDetails.class); - intent.putExtra("trackid", currentTrackId); - intent.putExtra("hasCancelButton", false); - startActivity(intent); + + if (currentTrackId > 0) { + Intent intent = new Intent(MyTracks.this, TrackDetails.class); + intent.putExtra("trackid", currentTrackId); + intent.putExtra("hasCancelButton", false); + startActivity(intent); + } } tryUnbindTrackRecordingService(); try { @@ -1259,44 +1173,12 @@ public class MyTracks extends TabActivity implements OnTouchListener, return results; } - String getSendToGoogleResultMessage() { - StringBuilder message = new StringBuilder(); - SendDialog sendToGoogleDialog = - dialogManager.getSendToGoogleDialog(); - if (sendToGoogleDialog.getSendToMyMaps()) { - message.append(sendToMyMapsMessage); - } - if (sendToGoogleDialog.getSendToFusionTables()) { - message.append(sendToFusionTablesMessage); - } - if (sendToGoogleDialog.getSendToDocs()) { - if (message.length() > 0) { - message.append(' '); - } - message.append(sendToDocsMessage); - } - if (sendToMyMapsSuccess && sendToFusionTablesSuccess && sendToDocsSuccess) { - message.append(' '); - message.append(getString(R.string.status_mymap_info)); - } - return message.toString(); - } - - /** - * Writes the selected track id to the shared preferences. - * Executed on the UI thread. - * - * @param trackId the id of the track - */ - public void setSelectedTrackId(final long trackId) { - ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges( - sharedPreferences - .edit() - .putLong(getString(R.string.selected_track_key), trackId)); + void clearSelectedTrack() { + dataHub.unloadCurrentTrack(); } long getSelectedTrackId() { - return selectedTrackId; + return dataHub.getSelectedTrackId(); } /** @@ -1419,7 +1301,7 @@ public class MyTracks extends TabActivity implements OnTouchListener, // @VisibleForTesting long getRecordingTrackId() { - return recordingTrackId; + return sharedPreferences.getLong(getString(R.string.recording_track_key), -1); } // @VisibleForTesting @@ -1436,4 +1318,8 @@ public class MyTracks extends TabActivity implements OnTouchListener, ITrackRecordingService getTrackRecordingService() { return trackRecordingService; } + + public TrackDataHub getDataHub() { + return dataHub; + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java deleted file mode 100644 index 4c71935fa..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java +++ /dev/null @@ -1,1096 +0,0 @@ -/* - * Copyright 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -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.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.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.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.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.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 - */ -public class MyTracksMap extends MapActivity - implements View.OnTouchListener, View.OnClickListener, - SharedPreferences.OnSharedPreferenceChangeListener { - private static final int TRACKPOINT_BUFFER_SIZE = 1024; - - // 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"; - - /** - * 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; - - /** - * True if the map should be scrolled so that the pointer is always in the - * visible area. - */ - 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. - */ - 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 LinearLayout messagePane; - private TextView messageText; - private LinearLayout busyPane; - private ImageButton optionsBtn; - - 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 - * return false here and still comply with the terms of service. - */ - @Override - protected boolean isRouteDisplayed() { - return false; - } - - /** - * We are displaying a location. This needs to return true in order to comply - * with the terms of service. - */ - @Override - protected boolean isLocationDisplayed() { - return true; - } - - // Application life cycle: - // ------------------------ - - @Override - public void onCreate(Bundle bundle) { - Log.d(TAG, "MyTracksMap.onCreate"); - super.onCreate(bundle); - - // The volume we want to control is the Text-To-Speech volume - int volumeStream = - new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream(); - setVolumeControlStream(volumeStream); - - providerUtils = MyTracksProviderUtils.Factory.get(this); - - // We don't need a window title bar: - requestWindowFeature(Window.FEATURE_NO_TITLE); - - // Inflate the layout: - setContentView(R.layout.mytracks_layout); - - // Remove the window's background because the MapView will obscure it - getWindow().setBackgroundDrawable(null); - - // Set up a map overlay: - screen = (RelativeLayout) findViewById(R.id.screen); - mapView = (MapView) findViewById(R.id.map); - mapView.requestFocus(); - mapOverlay = new MyTracksOverlay(this); - mapView.getOverlays().add(mapOverlay); - mapView.setOnTouchListener(this); - messagePane = (LinearLayout) findViewById(R.id.messagepane); - messageText = (TextView) findViewById(R.id.messagetext); - busyPane = (LinearLayout) findViewById(R.id.busypane); - optionsBtn = (ImageButton) findViewById(R.id.showOptions); - - 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"); - 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 { - currentLocation = null; - } - } - } - - // 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() { - optionsBtn.setVisibility( - isATrackSelected() ? View.VISIBLE : View.INVISIBLE); - } - - /** - * Tests if a location is visible. - * - * @param location a given location - * @return true if the given location is within the visible map area - */ - private boolean locationIsVisible(Location location) { - if (location == null || mapView == null) { - return false; - } - GeoPoint center = mapView.getMapCenter(); - int latSpan = mapView.getLatitudeSpan(); - int lonSpan = mapView.getLongitudeSpan(); - - // Bottom of map view is obscured by zoom controls/buttons. - // Subtract a margin from the visible area: - GeoPoint marginBottom = mapView.getProjection().fromPixels( - 0, mapView.getHeight()); - GeoPoint marginTop = mapView.getProjection().fromPixels(0, - mapView.getHeight() - - mapView.getZoomButtonsController().getZoomControls().getHeight()); - int margin = - Math.abs(marginTop.getLatitudeE6() - marginBottom.getLatitudeE6()); - GeoRect r = new GeoRect(center, latSpan, lonSpan); - r.top += margin; - - GeoPoint geoPoint = MyTracksUtils.getGeoPoint(location); - return r.contains(geoPoint); - } - - /** - * Moves the location pointer to the current location and center the map if - * the current location is outside the visible area. - */ - private void showCurrentLocation() { - if (currentLocation == null || mapOverlay == null || mapView == null) { - return; - } - mapOverlay.setMyLocation(currentLocation); - mapView.invalidate(); - if (keepMyLocationVisible && !locationIsVisible(currentLocation)) { - MapController controller = mapView.getController(); - GeoPoint geoPoint = MyTracksUtils.getGeoPoint(currentLocation); - controller.animateTo(geoPoint); - } - } - - /** - * Zooms and pans the map so that the given track is visible. - * - * @param trackId a given track ID - */ - public void showTrack(long trackId) { - if (mapView == null) { - return; - } - - Track track = providerUtils.getTrack(trackId); - if (track == null || track.getNumberOfPoints() < 2) { - return; - } - - TripStatistics stats = track.getStatistics(); - int bottom = stats.getBottom(); - int left = stats.getLeft(); - int latSpanE6 = stats.getTop() - bottom; - int lonSpanE6 = stats.getRight() - left; - if (latSpanE6 > 0 - && latSpanE6 < 180E6 - && lonSpanE6 > 0 - && lonSpanE6 < 360E6) { - keepMyLocationVisible = false; - GeoPoint center = new GeoPoint( - bottom + latSpanE6 / 2, - left + lonSpanE6 / 2); - if (MyTracksUtils.isValidGeoPoint(center)) { - mapView.getController().setCenter(center); - mapView.getController().zoomToSpan(latSpanE6, lonSpanE6); - } - } - } - - /** - * Zooms and pans the map so that the given waypoint is visible. - */ - public void showWaypoint(long waypointId) { - Waypoint wpt = providerUtils.getWaypoint(waypointId); - if (wpt != null && wpt.getLocation() != null) { - keepMyLocationVisible = false; - GeoPoint center = new GeoPoint( - (int) (wpt.getLocation().getLatitude() * 1E6), - (int) (wpt.getLocation().getLongitude() * 1E6)); - mapView.getController().setCenter(center); - mapView.getController().setZoom(20); - mapView.invalidate(); - } - } - - /** - * 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); - - if (selectedTrackId == trackId) { - // Selected track did not change, nothing to do. - mapOverlay.setTrackDrawingEnabled(isATrackSelected()); - updateOptionsButton(); - mapView.invalidate(); - return; - } - - if (trackId < 0) { - // Remove selection. - selectedTrackId = -1; - mapOverlay.setTrackDrawingEnabled(false); - mapOverlay.clearWaypoints(); - updateOptionsButton(); - mapView.invalidate(); - return; - } - - busyPane.setVisibility(View.VISIBLE); - selectedTrackId = trackId; - loadSelectedTrack(); - } - - 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 - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - menu.setHeaderTitle(R.string.tracklist_this_track); - menu.add(0, Constants.MENU_EDIT, 0, - R.string.tracklist_edit_track); - if (!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, - R.string.tracklist_share_track); - share.add(0, Constants.MENU_SHARE_LINK, 0, - R.string.tracklist_share_link); - share.add(0, Constants.MENU_SHARE_GPX_FILE, 0, - R.string.tracklist_share_gpx_file); - share.add(0, Constants.MENU_SHARE_KML_FILE, 0, - R.string.tracklist_share_kml_file); - share.add(0, Constants.MENU_SHARE_CSV_FILE, 0, - R.string.tracklist_share_csv_file); - share.add(0, Constants.MENU_SHARE_TCX_FILE, 0, - R.string.tracklist_share_tcx_file); - SubMenu save = menu.addSubMenu(0, - Constants.MENU_WRITE_TO_SD_CARD, 0, - R.string.tracklist_write_to_sd); - save.add(0, Constants.MENU_SAVE_GPX_FILE, 0, - R.string.tracklist_save_as_gpx); - save.add(0, Constants.MENU_SAVE_KML_FILE, 0, - R.string.tracklist_save_as_kml); - save.add(0, Constants.MENU_SAVE_CSV_FILE, 0, - R.string.tracklist_save_as_csv); - save.add(0, Constants.MENU_SAVE_TCX_FILE, 0, - R.string.tracklist_save_as_tcx); - menu.add(0, Constants.MENU_CLEAR_MAP, 0, - R.string.tracklist_clear_map); - menu.add(0, Constants.MENU_DELETE, 0, - R.string.tracklist_delete_track); - } - } - }; - - @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; - } - } - return false; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - myLocation = menu.add(0, Constants.MENU_MY_LOCATION, 0, - R.string.mylocation); - myLocation.setIcon(android.R.drawable.ic_menu_mylocation); - toggleLayers = menu.add(0, Constants.MENU_TOGGLE_LAYERS, 0, - R.string.switch_to_sat); - toggleLayers.setIcon(android.R.drawable.ic_menu_mapmode); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - toggleLayers.setTitle(mapView.isSatellite() ? - R.string.switch_to_map : R.string.switch_to_sat); - return super.onPrepareOptionsMenu(menu); - } - - @Override - 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; - } - return true; - } - case Constants.MENU_TOGGLE_LAYERS: { - toggleLayer(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onClick(View v) { - if (v == messagePane) { - launchMyLocationSettings(); - } else if (v == optionsBtn) { - optionsBtn.performLongClick(); - } - } - - /** - * We want the pointer to become visible again in case of the next location - * update: - */ - @Override - public boolean onTouch(View view, MotionEvent event) { - if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) { - if (!locationIsVisible(currentLocation)) { - keepMyLocationVisible = false; - } - } - return false; - } - - @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); - } - }); - } - } - - 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 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."); - } - } - - @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; - } - // 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(); - } - } - } - mapView.postInvalidate(); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java similarity index 93% rename from MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java rename to MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java index e16cd64dd..cc2ed1731 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java @@ -54,28 +54,14 @@ import java.util.Set; * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ -public class MyTracksSettings extends PreferenceActivity { - - public static final String SETTINGS_NAME = "MyTracksSettings"; - - /* - * Default values - keep in sync with those in preferences.xml. - */ - public static final int DEFAULT_AUTO_RESUME_TRACK_TIMEOUT = 10; // In min. - public static final int DEFAULT_ANNOUNCEMENT_FREQUENCY = -1; - public static final int DEFAULT_MAX_RECORDING_DISTANCE = 200; - public static final int DEFAULT_MIN_RECORDING_DISTANCE = 5; - public static final int DEFAULT_MIN_RECORDING_INTERVAL = 0; - public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200; - public static final int DEFAULT_SPLIT_FREQUENCY = 0; +public class SettingsActivity extends PreferenceActivity { private BackupPreferencesListener backupListener; - private SharedPreferences preferences; /** Called when the activity is first created. */ @Override - public void onCreate(Bundle icicle) { + protected void onCreate(Bundle icicle) { super.onCreate(icicle); // The volume we want to control is the Text-To-Speech volume @@ -86,7 +72,7 @@ public class MyTracksSettings extends PreferenceActivity { // Tell it where to read/write preferences PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setSharedPreferencesName(SETTINGS_NAME); + preferenceManager.setSharedPreferencesName(Constants.SETTINGS_NAME); preferenceManager.setSharedPreferencesMode(0); // Set up automatic preferences backup @@ -219,7 +205,7 @@ public class MyTracksSettings extends PreferenceActivity { @Override public boolean onPreferenceClick(Preference preference) { BackupActivityHelper backupHelper = - new BackupActivityHelper(MyTracksSettings.this); + new BackupActivityHelper(SettingsActivity.this); backupHelper.writeBackup(); return true; } @@ -229,7 +215,7 @@ public class MyTracksSettings extends PreferenceActivity { @Override public boolean onPreferenceClick(Preference preference) { BackupActivityHelper backupHelper = - new BackupActivityHelper(MyTracksSettings.this); + new BackupActivityHelper(SettingsActivity.this); backupHelper.restoreBackup(); return true; } @@ -373,7 +359,7 @@ public class MyTracksSettings extends PreferenceActivity { public void run() { // Give feedback to the user. Toast.makeText( - MyTracksSettings.this, + SettingsActivity.this, R.string.settings_reset_done, Toast.LENGTH_SHORT).show(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index da9165cbc..3ddab3bbd 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.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 new file mode 100644 index 000000000..d165f0e1e --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDataHub.java @@ -0,0 +1,1011 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks; + +import static com.google.android.apps.mytracks.Constants.TAG; + +import com.google.android.apps.mytracks.TrackDataListener.ProviderState; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.TrackPointsColumns; +import com.google.android.apps.mytracks.content.TracksColumns; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.content.WaypointsColumns; +import com.google.android.apps.mytracks.services.TrackRecordingService; +import com.google.android.apps.mytracks.util.ApiFeatures; +import com.google.android.apps.mytracks.util.LocationUtils; +import com.google.android.maps.mytracks.R; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +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.util.Log; +import android.widget.Toast; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Track data hub, which receives data (both live and recorded) from many + * different sources and distributes it to those interested after some standard + * processing. + * + * @author Rodrigo Damazio + */ +public class TrackDataHub { + + private static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute + private static final long MAX_NETWORK_AGE_MS = 1000 * 60 * 10; // 10 minutes + + // Preference keys + private final String SELECTED_TRACK_KEY; + private final String RECORDING_TRACK_KEY; + private final String MIN_REQUIRED_ACCURACY_KEY; + private final String METRIC_UNITS_KEY; + private final String SPEED_REPORTING_KEY; + + // Application services + private final Context context; + private final MyTracksProviderUtils providerUtils; + + // System services + private final SensorManager sensorManager; + private final LocationManager locationManager; + private final SharedPreferences sharedPreferences; + private final ContentResolver contentResolver; + + // Internal listeners (to receive data from the system) + private final ContentObserver pointObserver; + private final ContentObserver waypointObserver; + private final ContentObserver trackObserver; + private final LocationListener locationListener; + private final OnSharedPreferenceChangeListener preferenceListener; + private final SensorEventListener compassListener; + + // External listeners (to pass data to activities) + private final Set registeredListeners = + new LinkedHashSet(); + + // Get content notifications on the main thread, send listener callbacks in another. + // This ensures listener calls are serialized. + private final HandlerThread listenerHandlerThread; + private final Handler listenerHandler; + private boolean started; + + // Cached preference values + private int minRequiredAccuracy; + private boolean useMetricUnits; + private boolean reportSpeed; + + // Cached sensor readings + private float declination; + private long lastDeclinationUpdate; + private float lastSeenMagneticHeading; + private Location lastSeenLocation; + private boolean hasFix; + private boolean hasGoodFix; + + // Transient state about the selected track + private long firstSeenLocationId; + private long lastSeenLocationId; + private long selectedTrackId; + private long recordingTrackId; + private int numLoadedPoints; + private boolean hasProviderEnabled; + + /** Callback for when the tracks table is updated. */ + private class TrackObserverCallback implements Runnable { + @Override + public void run() { + notifyTrackUpdated(getRegisteredListenerArray()); + } + } + + /** Callback for when the waypoints table is updated. */ + private class WaypointObserverCallback implements Runnable { + @Override + public void run() { + notifyWaypointUpdated(getRegisteredListenerArray()); + } + } + + /** Callback for when the points table is updated. */ + private class PointObserverCallback implements Runnable { + @Override + public void run() { + notifyPointsUpdated(true, getRegisteredListenerArray()); + } + } + + /** Listener for when preferences change. */ + private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + notifyPreferenceChanged(key); + } + } + + /** + * Generic content observer which will call a given {@link Runnable} in the + * given handler if the content has changed and we're recording the selected + * track. + */ + private class TrackContentObserver extends ContentObserver { + private final Runnable callback; + + public TrackContentObserver(Handler contentHandler, Runnable callback) { + super(contentHandler); + + this.callback = callback; + } + + @Override + public void onChange(boolean selfChange) { + Log.v(TAG, "TrackContentObserver.onChange"); + + // We want to filter only updates from the selected track, but since + // we can't see what the update is, we'll let two cases pass: + // 1 - The point(s) was(ere) changed because it's a recording track + // (and thus we care about it if the recording is the selected one) + // 2 - The point(s) was(ere) changed because it's syncing a track + // (and thus there will be no new points for the selected one) + if (!isRecordingSelected()) { + return; + } + + // Update can potentially be lengthy, put it in its own thread: + listenerHandler.post(callback); + } + } + + /** Listener for the current location (independent from track data). */ + private class CurrentLocationListener implements + LocationListener { + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + if (!LocationManager.GPS_PROVIDER.equals(provider)) return; + + hasProviderEnabled = (status == LocationProvider.AVAILABLE); + notifyFixType(); + } + + @Override + public void onProviderEnabled(String provider) { + if (!LocationManager.GPS_PROVIDER.equals(provider)) return; + + hasProviderEnabled = true; + notifyFixType(); + } + + @Override + public void onProviderDisabled(String provider) { + if (!LocationManager.GPS_PROVIDER.equals(provider)) return; + + hasProviderEnabled = false; + notifyFixType(); + } + + @Override + public void onLocationChanged(Location location) { + notifyLocationChanged(location); + } + } + + /** Listener for compass readings. */ + private class CompassListener implements + SensorEventListener { + @Override + public void onSensorChanged(SensorEvent event) { + lastSeenMagneticHeading = event.values[0]; + maybeUpdateDeclination(); + notifyHeadingChanged(getRegisteredListenerArray()); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Do nothing + } + } + + public TrackDataHub(Context ctx, MyTracksProviderUtils providerUtils) { + this.context = ctx; + this.providerUtils = providerUtils; + + SELECTED_TRACK_KEY = context.getString(R.string.selected_track_key); + RECORDING_TRACK_KEY = context.getString(R.string.recording_track_key); + MIN_REQUIRED_ACCURACY_KEY = context.getString(R.string.min_required_accuracy_key); + METRIC_UNITS_KEY = context.getString(R.string.metric_units_key); + SPEED_REPORTING_KEY = context.getString(R.string.report_speed_key); + + listenerHandlerThread = new HandlerThread("trackDataContentThread"); + listenerHandlerThread.start(); + listenerHandler = new Handler(listenerHandlerThread.getLooper()); + + sharedPreferences = ctx.getSharedPreferences(Constants.SETTINGS_NAME, 0); + preferenceListener = new HubSharedPreferenceListener(); + + sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE); + locationManager = + (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + + contentResolver = ctx.getContentResolver(); + Handler contentHandler = new Handler(); + pointObserver = new TrackContentObserver(contentHandler, new PointObserverCallback()); + waypointObserver = new TrackContentObserver(contentHandler, new WaypointObserverCallback()); + trackObserver = new TrackContentObserver(contentHandler, new TrackObserverCallback()); + + compassListener = new CompassListener(); + locationListener = new CurrentLocationListener(); + } + + /** + * Starts listening to data sources and reporting the data to external + * listeners. + */ + public void start() { + Log.i(TAG, "TrackDataHub.start"); + if (started) { + Log.w(TAG, "Already started, ignoring"); + return; + } + started = true; + + sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceListener); + selectedTrackId = sharedPreferences.getLong(SELECTED_TRACK_KEY, -1); + recordingTrackId = sharedPreferences.getLong(RECORDING_TRACK_KEY, -1); + useMetricUnits = sharedPreferences.getBoolean(METRIC_UNITS_KEY, true); + reportSpeed = sharedPreferences.getBoolean(SPEED_REPORTING_KEY, true); + minRequiredAccuracy = sharedPreferences.getInt(MIN_REQUIRED_ACCURACY_KEY, + Constants.DEFAULT_MIN_REQUIRED_ACCURACY); + + if (recordingTrackId > 0) { + Intent startIntent = new Intent(context, TrackRecordingService.class); + context.startService(startIntent); + } + + // This may or may not register internal listeners, depending on whether + // we already had external listeners. + updateInternalListeners(); + + // If there were listeners already registered, make sure they become up-to-date. + // TODO: This should really only send new data (in a start-stop-start cycle). + reloadDataFor(getRegisteredListenerArray()); + } + + /** + * Stops listening to data sources and reporting the data to external + * listeners. + */ + public void stop() { + Log.i(TAG, "TrackDataHub.stop"); + if (!started) { + Log.w(TAG, "Not started, ignoring"); + return; + } + + sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceListener); + + // Unregister internal listeners even if there are external listeners registered. + unregisterInternalListeners(); + + started = false; + } + + /** Permanently invalidates and throws away all resources used by this class. */ + public void destroy() { + if (started) { + throw new IllegalStateException("Can only destroy the data hub after it's been stopped"); + } + + listenerHandlerThread.quit(); + } + + /** Updates known magnetic declination if needed. */ + private void maybeUpdateDeclination() { + if (lastSeenLocation == null) { + // We still don't know where we are. + return; + } + + // Update the variation every hour + long now = System.currentTimeMillis(); + if (now - lastDeclinationUpdate < 60 * 60 * 1000) { + return; + } + + lastDeclinationUpdate = now; + long timestamp = lastSeenLocation.getTime(); + if (timestamp == 0) { + // Hack for Samsung phones which don't populate the time field + timestamp = now; + } + + GeomagneticField field = new GeomagneticField( + (float) lastSeenLocation.getLatitude(), + (float) lastSeenLocation.getLongitude(), + (float) lastSeenLocation.getAltitude(), + timestamp); + declination = field.getDeclination(); + } + + /** + * Forces the current location to be updated and reported to all listeners. + * The reported location may be from the network provider if the GPS provider + * is not available or doesn't have a fix. + */ + public void forceUpdateLocation() { + checkStarted(); + + // TODO: Let's look at more advanced algorithms to determine the best + // current location. + if (locationManager == null) { + return; + } + + final long now = System.currentTimeMillis(); + Location loc = locationManager.getLastKnownLocation( + Constants.GPS_PROVIDER); + if (loc == null || loc.getTime() < now - MAX_LOCATION_AGE_MS) { + // We don't have a recent GPS fix, just use cell towers if available + loc = locationManager.getLastKnownLocation( + LocationManager.NETWORK_PROVIDER); + if (loc == null || loc.getTime() < now - MAX_NETWORK_AGE_MS) { + // We don't have a recent cell tower location, let the user know: + Toast.makeText(context, context.getString(R.string.status_no_location), + Toast.LENGTH_LONG).show(); + return; + } else { + // Let the user know we have only an approximate location: + Toast.makeText(context, context.getString(R.string.status_approximate_location), + Toast.LENGTH_LONG).show(); + } + } + + notifyLocationChanged(loc, getRegisteredListenerArray()); + } + + /** Returns the ID of the currently-selected track. */ + public long getSelectedTrackId() { + checkStarted(); + return selectedTrackId; + } + + /** Returns whether there's a track currently selected. */ + public boolean isATrackSelected() { + checkStarted(); + return selectedTrackId > 0; + } + + /** Returns whether we're currently recording a track. */ + public boolean isRecording() { + checkStarted(); + return recordingTrackId > 0; + } + + /** Returns whether the selected track is still being recorded. */ + public boolean isRecordingSelected() { + checkStarted(); + return isRecording() && recordingTrackId == selectedTrackId; + } + + /** + * Loads the given track and makes it the currently-selected one. + * It is ok to call this method before {@link start}, and in that case + * the data will only be passed to listeners when {@link start} is called. + * + * @param trackId the ID of the track to load + */ + public void loadTrack(long trackId) { + if (trackId == selectedTrackId) { + Log.w(TAG, "Not reloading track, id=" + trackId); + return; + } + + // Save the selection to memory and flash. + ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges( + sharedPreferences.edit().putLong(SELECTED_TRACK_KEY, trackId)); + selectedTrackId = trackId; + + // Force it to reload data from the beginning. + firstSeenLocationId = -1; + lastSeenLocationId = -1; + numLoadedPoints = 0; + + reloadDataFor(getRegisteredListenerArray()); + } + + /** + * Unloads the currently-selected track. + */ + public void unloadCurrentTrack() { + loadTrack(-1); + } + + /** + * Registers a listener to send data to. + * It is ok to call this method before {@link start}, and in that case + * the data will only be passed to listeners when {@link start} is called. + * + * @param listener the listener to register + */ + public void registerTrackDataListener(final TrackDataListener listener) { + Log.d(TAG, "Registered track data listener: " + listener); + synchronized (registeredListeners) { + registeredListeners.add(listener); + + // Don't load any data or start internal listeners if start() hasn't been + // called. When it is called, we'll do both things. + if (!started) return; + + reloadDataFor(listener); + } + + updateInternalListeners(); + } + + /** + * Unregisters a listener to send data to. + * + * @param listener the listener to unregister + */ + public void unregisterTrackDataListener(TrackDataListener listener) { + Log.d(TAG, "Unregistered track data listener: " + listener); + synchronized (registeredListeners) { + registeredListeners.remove(listener); + + // Don't load any data or start internal listeners if start() hasn't been + // called. When it is called, we'll do both things. + if (!started) return; + } + updateInternalListeners(); + } + + /** Updates the internal (sensor, position, etc) listeners. */ + private void updateInternalListeners() { + boolean hasListeners; + synchronized (registeredListeners) { + hasListeners = registeredListeners.isEmpty(); + } + + if (hasListeners) { + unregisterInternalListeners(); + } else { + registerInternalListeners(); + } + } + + /** Registers all internal (sensor, position, etc.) listeners. */ + private void registerInternalListeners() { + // Listen to data provider + contentResolver.registerContentObserver( + TracksColumns.CONTENT_URI, false, trackObserver); + contentResolver.registerContentObserver( + WaypointsColumns.CONTENT_URI, false, waypointObserver); + contentResolver.registerContentObserver( + TrackPointsColumns.CONTENT_URI, false, pointObserver); + + // Listen to compass + Sensor compass = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + if (compass != null) { + Log.d(Constants.TAG, + "TrackDataHub: Now registering sensor listeners."); + sensorManager.registerListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI); + } + + // Listen to GPS + LocationProvider gpsProvider = + locationManager.getProvider(Constants.GPS_PROVIDER); + if (gpsProvider == null) { + Toast.makeText(context, R.string.error_no_gps_location_provider, Toast.LENGTH_LONG).show(); + hasProviderEnabled = false; + return; + } else { + Log.d(Constants.TAG, "TrackDataHub: Using location provider " + + gpsProvider.getName()); + } + locationManager.requestLocationUpdates(gpsProvider.getName(), + 0 /*minTime*/, 0 /*minDist*/, locationListener); + hasProviderEnabled = locationManager.isProviderEnabled(Constants.GPS_PROVIDER); + + // Listen to network location + 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(Constants.TAG, + "Could not register network location listener."); + } + } + + /** Unregisters all internal (sensor, position, etc.) listeners. */ + private void unregisterInternalListeners() { + locationManager.removeUpdates(locationListener); + sensorManager.unregisterListener(compassListener); + contentResolver.unregisterContentObserver(trackObserver); + contentResolver.unregisterContentObserver(waypointObserver); + contentResolver.unregisterContentObserver(pointObserver); + } + + /** + * Reloads all track data received so far into the specified listeners. + */ + public void reloadDataFor(final TrackDataListener... listeners) { + if (listeners.length == 0) { + Log.d(TAG, "No listeners, not reloading"); + return; + } + if (!started) { + Log.w(TAG, "Not started, not reloading"); + return; + } + + listenerHandler.post(new Runnable() { + @Override + public void run() { + Track track = providerUtils.getTrack(selectedTrackId); + + // Ignore the return values here, we're already sending the full data set anyway + for (TrackDataListener listener : listeners) { + listener.onUnitsChanged(useMetricUnits); + listener.onReportSpeedChanged(reportSpeed); + } + + notifySelectedTrackChanged(track, listeners); + notifyPointsCleared(listeners); + notifyPointsUpdated(false, listeners); + notifyWaypointUpdated(listeners); + + if (lastSeenLocation != null) { + notifyLocationChanged(lastSeenLocation, true, listeners); + } else { + notifyFixType(); + } + + notifyHeadingChanged(listeners); + } + }); + } + + /** + * Called when a preference changes. + * + * @param key the key to the preference that changed + */ + private void notifyPreferenceChanged(String key) { + 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, + Constants.DEFAULT_MIN_REQUIRED_ACCURACY); + } else if (key.equals(METRIC_UNITS_KEY)) { + useMetricUnits = sharedPreferences.getBoolean(METRIC_UNITS_KEY, true); + notifyUnitsChanged(); + } else if (key.equals(SPEED_REPORTING_KEY)) { + reportSpeed = sharedPreferences.getBoolean(SPEED_REPORTING_KEY, true); + notifySpeedReportingChanged(); + } + } + + /** Called when the speed/pace reporting preference changes. */ + private void notifySpeedReportingChanged() { + listenerHandler.post(new Runnable() { + @Override + public void run() { + TrackDataListener[] listeners = getRegisteredListenerArray(); + + boolean reloadData = false; + for (TrackDataListener listener : listeners) { + reloadData |= listener.onReportSpeedChanged(reportSpeed); + } + + if (reloadData) { + reloadDataFor(listeners); + } + } + }); + } + + /** Called when the metric units setting changes. */ + private void notifyUnitsChanged() { + listenerHandler.post(new Runnable() { + @Override + public void run() { + TrackDataListener[] listeners = getRegisteredListenerArray(); + + boolean reloadData = false; + for (TrackDataListener listener : listeners) { + reloadData |= listener.onUnitsChanged(useMetricUnits); + } + + if (reloadData) { + reloadDataFor(listeners); + } + } + }); + } + + /** Notifies about the current GPS fix state. */ + private void notifyFixType() { + final TrackDataListener.ProviderState state; + if (!hasProviderEnabled) { + state = ProviderState.DISABLED; + } else if (!hasFix) { + state = ProviderState.NO_FIX; + } else if (!hasGoodFix) { + state = ProviderState.BAD_FIX; + } else { + state = ProviderState.GOOD_FIX; + } + + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : registeredListeners) { + listener.onProviderStateChange(state); + } + } + }); + } + + /** + * Notifies the the current location has changed, without any filtering. + * If the state of GPS fix has changed, that will also be reported. + * + * @param location the current location + * @param listeners the listeners to notify + */ + private void notifyLocationChanged(Location location, TrackDataListener... listeners) { + notifyLocationChanged(location, false, listeners); + } + + /** + * Notifies that the current location has changed, without any filtering. + * If the state of GPS fix has changed, that will also be reported. + * + * @param location the current location + * @param forceUpdate whether to force the notifications to happen + * @param listeners the listeners to notify + */ + private void notifyLocationChanged(final Location location, boolean forceUpdate, final TrackDataListener... listeners) { + if (location == null) return; + + boolean isGpsLocation = location.getProvider().equals(LocationManager.GPS_PROVIDER); + + boolean oldHasFix = hasFix; + boolean oldHasGoodFix = hasGoodFix; + + // We consider a good fix to be a recent one with reasonable accuracy. + if (isGpsLocation) { + lastSeenLocation = location; + hasFix = (location != null && System.currentTimeMillis() - location.getTime() <= MAX_LOCATION_AGE_MS); + hasGoodFix = (location != null && location.getAccuracy() <= minRequiredAccuracy); + if (hasFix != oldHasFix || hasGoodFix != oldHasGoodFix || forceUpdate) { + notifyFixType(); + } + } + + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : listeners) { + listener.onCurrentLocationChanged(location); + } + } + }); + } + + /** + * Notifies that the current heading has changed. + * + * @param listeners the listeners to notify + */ + private void notifyHeadingChanged(final TrackDataListener... listeners) { + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : listeners) { + listener.onCurrentHeadingChanged(lastSeenMagneticHeading + declination); + } + } + }); + } + + /** + * Notifies that a new track has been selected.. + * + * @param track the new selected track + * @param listeners the listeners to notify + */ + private void notifySelectedTrackChanged(final Track track, final TrackDataListener... listeners) { + Log.d(TAG, "New track selected, id=" + (track != null ? track.getId() : "none")); + + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : listeners) { + listener.onSelectedTrackChanged(track, isRecordingSelected()); + + if (track != null) { + listener.onTrackUpdated(track); + } + } + } + }); + } + + /** + * Notifies that the currently-selected track's data has been updated. + * + * @param listeners the listeners to notify + */ + private void notifyTrackUpdated(final TrackDataListener... listeners) { + final Track track = providerUtils.getTrack(selectedTrackId); + + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : registeredListeners) { + listener.onTrackUpdated(track); + } + } + }); + } + + /** + * Notifies that waypoints have been updated. + * We assume few waypoints, so we reload them all every time. + * + * @param listeners the listeners to notify + */ + private void notifyWaypointUpdated(final TrackDataListener... listeners) { + // Always reload all the waypoints. + final Cursor cursor = providerUtils.getWaypointsCursor( + selectedTrackId, 0, Constants.MAX_DISPLAYED_WAYPOINTS_POINTS); + + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : registeredListeners) { + listener.clearWaypoints(); + } + + try { + if (cursor != null && cursor.moveToFirst()) { + do { + Waypoint waypoint = providerUtils.createWaypoint(cursor); + if (!LocationUtils.isValidLocation(waypoint.getLocation())) { + continue; + } + + for (TrackDataListener listener : listeners) { + listener.onNewWaypoint(waypoint); + } + } while (cursor.moveToNext()); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + for (TrackDataListener listener : listeners) { + listener.onNewWaypointsDone(); + } + } + }); + } + + /** + * Tells listeners to clear the current list of points. + * + * @param listeners the listeners to notify + */ + private void notifyPointsCleared(final TrackDataListener... listeners) { + listenerHandler.post(new Runnable() { + @Override + public void run() { + for (TrackDataListener listener : listeners) { + listener.clearTrackPoints(); + } + } + }); + } + + /** + * Notifies the given listeners about track points in the given ID range. + * + * @param minPointId the first point ID to notify, inclusive + * @param maxPointId the last poind ID to notify, inclusive + * @param keepState whether to load and save state about the already-notified points. + * If true, only new points are reported. + * If false, then the whole track will be loaded, without affecting the store. + * @param listeners the listeners to notify + */ + private void notifyPointsUpdated(final boolean keepState, final TrackDataListener... listeners) { + listenerHandler.post(new Runnable() { + @Override + public void run() { + notifyPointsUpdatedSync(keepState, listeners); + } + }); + } + + /** + * Asynchronous version of the above method. + */ + private void notifyPointsUpdatedSync(boolean keepState, TrackDataListener[] listeners) { + // If we're loading state, start from after the last seen point up to the last recorded one + // (all new points) + // If we're not loading state, then notify about all the previously-seen points. + long minPointId = keepState ? lastSeenLocationId + 1 : 0; + long maxPointId = keepState ? -1 : lastSeenLocationId; + + if (numLoadedPoints >= 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). We do this for every listener even if we were loading just for + // a few of them (why miss the oportunity?). + + firstSeenLocationId = -1; + lastSeenLocationId = -1; + numLoadedPoints = 0; + listeners = getRegisteredListenerArray(); + maxPointId = -1; + minPointId = 0; + keepState = true; + + for (TrackDataListener listener : listeners) { + listener.clearTrackPoints(); + } + } + + // Keep the originally selected track ID so we can stop if it changes. + long currentSelectedTrackId = selectedTrackId; + + // If we're ignoring state, start from the beginning of the track + int localNumLoadedPoints = keepState ? numLoadedPoints : 0; + long localFirstSeenLocationId = keepState ? firstSeenLocationId : 0; + long localLastSeenLocationId = minPointId; + long lastStoredLocationId = providerUtils.getLastLocationId(currentSelectedTrackId); + int pointSamplingFrequency = -1; + + // Create a double-buffering location provider. + MyTracksProviderUtils.DoubleBufferedLocationFactory locationFactory = + new MyTracksProviderUtils.DoubleBufferedLocationFactory(); + LocationIterator it = providerUtils.getLocationIterator( + currentSelectedTrackId, minPointId, false, locationFactory); + + while (it.hasNext()) { + if (currentSelectedTrackId != selectedTrackId) { + // The selected track changed beneath us, stop. + break; + } + + Location location = it.next(); + long locationId = it.getLocationId(); + + // If past the last wanted point, stop. + // This happens when adding a new listener after data has already been loaded, + // in which case we only want to bring that listener up to the point where the others + // were. In case it does happen, we should be wasting few points (only the ones not + // yet notified to other listeners). + if (maxPointId > 0 && locationId > maxPointId) { + break; + } + + if (localFirstSeenLocationId == -1) { + // This was our first point, keep its ID + localFirstSeenLocationId = locationId; + } + + if (pointSamplingFrequency == -1) { + // Now we already have at least one point, calculate the sampling + // frequency. + long numTotalPoints = lastStoredLocationId - localFirstSeenLocationId; + pointSamplingFrequency = + (int) (1 + numTotalPoints / Constants.TARGET_DISPLAYED_TRACK_POINTS); + } + + notifyNewPoint(location, locationId, lastStoredLocationId, + localNumLoadedPoints, pointSamplingFrequency, listeners); + + localNumLoadedPoints++; + localLastSeenLocationId = locationId; + } + it.close(); + + if (keepState) { + numLoadedPoints = localNumLoadedPoints; + firstSeenLocationId = localFirstSeenLocationId; + lastSeenLocationId = localLastSeenLocationId; + } + + for (TrackDataListener listener : listeners) { + listener.onNewTrackPointsDone(); + } + } + + private void notifyNewPoint(Location location, + long locationId, + long lastStoredLocationId, + int numLoadedPoints, + int pointSamplingFrequency, + TrackDataListener[] listeners) { + boolean isValid = LocationUtils.isValidLocation(location); + if (!isValid) { + // Invalid points are segment splits - report those separately. + // TODO: Always send last valid point before and first valid point after a split + for (TrackDataListener listener : listeners) { + listener.onSegmentSplit(); + } + return; + } + + // 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. + boolean includeInSample = + (numLoadedPoints % pointSamplingFrequency == 0 || + (!isRecordingSelected() && locationId == lastStoredLocationId)); + + if (!includeInSample) { + for (TrackDataListener listener : listeners) { + listener.onSampledOutTrackPoint(location); + } + return; + } + + // Point is valid and included in sample. + for (TrackDataListener listener : listeners) { + // No need to allocate a new location (we can safely reuse the existing). + listener.onNewTrackPoint(location); + } + } + + /** Returns an array with all the currently-registered listeners. */ + private TrackDataListener[] getRegisteredListenerArray() { + synchronized (registeredListeners) { + TrackDataListener listenerArray[] = new TrackDataListener[registeredListeners.size()]; + return registeredListeners.toArray(listenerArray); + } + } + + /** Verifies that {@link #start} has been called. */ + private void checkStarted() { + if (!started) { + throw new IllegalStateException("TrackDataHub has not been started."); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDataListener.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDataListener.java new file mode 100644 index 000000000..96c79ae5c --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDataListener.java @@ -0,0 +1,169 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks; + +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.Waypoint; + +import android.location.Location; + +/** + * Listener for track data, for both initial and incremental loading. + * + * TODO: Allow listening to just part of the data received here. + * + * @author Rodrigo Damazio + */ +public interface TrackDataListener { + + /** States for the GPS location provider. */ + public enum ProviderState { + DISABLED, + NO_FIX, + BAD_FIX, + GOOD_FIX; + } + + /** + * Called when the location provider changes state. + */ + void onProviderStateChange(ProviderState state); + + /** + * Called when the current location changes. + * This is meant for immediate location display only - track point data is + * delivered by other methods below, such as {@link #onNewTrackPoint}. + * + * @param loc the last known location + */ + void onCurrentLocationChanged(Location loc); + + /** + * Called when the current heading changes. + * + * @param heading the current heading, already accounting magnetic declination + */ + void onCurrentHeadingChanged(double heading); + + // This is always followed by calls to onTrackUpdated/onNewTrackPoints/onWaypointsChanged + // The recipient has a chance to unregister here before that happens. + + /** + * Called when the currently-selected track changes. + * This will be followed by calls to data methods such as + * {@link #onTrackUpdated}, {@link #clearTrackPoints}, + * {@link #onNewTrackPoint(Location)}, etc., even if no track is currently + * selected (in which case you'll only get calls to clear the current data). + * + * @param track the selected track, or null if no track is selected + * @param isRecording whether we're currently recording the selected track + */ + void onSelectedTrackChanged(Track track, boolean isRecording); + + // Called when the already-selected track gets updated + + /** + * Called when the track and/or its statistics have been updated. + * + * @param track the updated version of the track + */ + void onTrackUpdated(Track track); + + // Map, chart - points, waypoints + // Stats - track + // Points are loaded progressively in batches + // Track is always loaded + // Waypoints are always fully loaded (limit to MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS) + + + // Initial load - clear + N * onNewTrackPoint + onNewTrackPointsDone + // New points - onNewTrackPoint + onNewTrackPointsDone + // Points passed in are already sampled (but can be invalid to denote a split) + // If needs to resample, repeats initial load + + /** + * Called to clear any previously-sent track points. + * This can be called at any time that we decide the data needs to be + * reloaded, such as when it needs to be resampled. + */ + void clearTrackPoints(); + + /** + * Called when a new interesting track point is read. + * In this case, interesting means that the point has already undergone + * sampling and invalid point filtering. + * + * @param loc the new track point + */ + void onNewTrackPoint(Location loc); + + /** + * Called when a uninteresting track point is read. + * Uninteresting points are all points that get sampled out of the track. + * + * @param loc the new track point + */ + void onSampledOutTrackPoint(Location loc); + + /** + * Called when an invalid point (representing a segment split) is read. + */ + void onSegmentSplit(); + + /** + * Called when we're done (for the time being) sending new points. + * This gets called after every batch of calls to {@link #onNewTrackPoint}, + * {@link #onSampledOutTrackPoint} and {@link #onSegmentSplit}. + */ + void onNewTrackPointsDone(); + + /** + * Called to clear any previously-sent waypoints. + * This can be called at any time that we decide the data needs to be + * reloaded. + */ + void clearWaypoints(); + + /** + * Called when a new waypoint is read. + * + * @param wpt the new waypoint + */ + void onNewWaypoint(Waypoint wpt); + + /** + * Called when we're done (for the time being) sending new waypoints. + * This gets called after every batch of calls to {@link #clearWaypoints} and + * {@link #onNewWaypoint}. + */ + void onNewWaypointsDone(); + + /** + * Called when the display units are changed by the user. + * + * @param metric true if the units are metric, false if imperial + * @return true to reload all the data, false otherwise + */ + boolean onUnitsChanged(boolean metric); + + /** + * Called when the speed/pace display unit is changed by the user. + * + * @param reportSpeed true to report speed, false for pace + * @return true to reload all the data, false otherwise + */ + boolean onReportSpeedChanged(boolean reportSpeed); +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java index cf5004650..990062836 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java @@ -202,7 +202,7 @@ public class TrackList extends ListActivity findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this); SharedPreferences preferences = - getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getSharedPreferences(Constants.SETTINGS_NAME, 0); preferences.registerOnSharedPreferenceChangeListener(this); metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true); diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java index d26b9a87b..a4a32436d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java @@ -65,7 +65,7 @@ public class WaypointDetails extends Activity utils = new StatsUtilities(this); SharedPreferences preferences = - getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getSharedPreferences(Constants.SETTINGS_NAME, 0); if (preferences != null) { boolean useMetric = preferences.getBoolean(getString(R.string.metric_units_key), true); diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java index 8265f9b9e..cbaad0481 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java @@ -59,7 +59,6 @@ public class WaypointsList extends ListActivity private Button insertWaypointButton = null; private Button insertStatisticsButton = null; private long recordingTrackId = -1; - private long selectedTrackId = -1; private MyTracksProviderUtils providerUtils; private Cursor waypointsCursor = null; @@ -139,7 +138,10 @@ public class WaypointsList extends ListActivity (Button) findViewById(R.id.waypointslist_btn_insert_statistics); insertStatisticsButton.setOnClickListener(this); SharedPreferences preferences = - getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getSharedPreferences(Constants.SETTINGS_NAME, 0); + + // TODO: Get rid of selected and recording track IDs + long selectedTrackId = -1; if (preferences != null) { recordingTrackId = preferences.getLong(getString(R.string.recording_track_key), -1); diff --git a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java index 45251a3b7..113253c5c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java @@ -15,7 +15,7 @@ */ package com.google.android.apps.mytracks; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -82,6 +82,6 @@ public class WelcomeActivity extends Activity { AlertDialog dialog = builder.create(); dialog.show(); ((TextView) dialog.findViewById(R.id.about_version_register)). - setText(MyTracksUtils.getMyTracksVersion(this)); + setText(SystemUtils.getMyTracksVersion(this)); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java index 22e974b2d..5105285e4 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToDocs.java @@ -17,7 +17,6 @@ package com.google.android.apps.mytracks.io; import com.google.android.apps.mytracks.MyTracks; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.io.docs.DocsHelper; @@ -73,7 +72,7 @@ public class SendToDocs { this.trackId = trackId; SharedPreferences preferences = activity.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); if (preferences != null) { metricUnits = preferences.getBoolean(activity.getString(R.string.metric_units_key), diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java index adc7c31ed..eeb769b88 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.io; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.ProgressIndicator; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; @@ -25,8 +24,9 @@ import com.google.android.apps.mytracks.io.gdata.GDataWrapper; import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction; import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.maps.mytracks.R; import com.google.api.client.googleapis.GoogleHeaders; @@ -141,7 +141,7 @@ public class SendToFusionTables implements Runnable { this.providerUtils = MyTracksProviderUtils.Factory.get(context); GoogleHeaders headers = new GoogleHeaders(); - headers.setApplicationName("Google-MyTracks-" + MyTracksUtils.getMyTracksVersion(context)); + headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context)); headers.gdataVersion = GDATA_VERSION; transport = new HttpTransport(); @@ -296,7 +296,7 @@ public class SendToFusionTables implements Runnable { private boolean uploadAllTrackPoints(final Track track, String originalDescription) { - SharedPreferences preferences = context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0); boolean metricUnits = true; if (preferences != null) { metricUnits = preferences.getBoolean(context.getString(R.string.metric_units_key), true); @@ -344,7 +344,7 @@ public class SendToFusionTables implements Runnable { } // Add to the elevation profile. - if (loc != null && MyTracksUtils.isValidLocation(loc)) { + if (loc != null && LocationUtils.isValidLocation(loc)) { // All points go into the smoothing buffer... elevationBuffer.setNext(metricUnits ? loc.getAltitude() : loc.getAltitude() * UnitConversions.M_TO_FT); @@ -566,12 +566,12 @@ public class SendToFusionTables implements Runnable { * Decimate to 2 meter precision. Fusion tables doesn't like too many * points: */ - MyTracksUtils.decimate(segment, 2.0); + LocationUtils.decimate(segment, 2.0); /* If the track still has > 2500 points, split it in pieces: */ final int maxPoints = 2500; if (segment.getLocations().size() > maxPoints) { - splitTracks.addAll(MyTracksUtils.split(segment, maxPoints)); + splitTracks.addAll(LocationUtils.split(segment, maxPoints)); } else if (segment.getLocations().size() >= 2) { splitTracks.add(segment); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java index 4145f3a6f..ffc895da6 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java @@ -17,7 +17,6 @@ package com.google.android.apps.mytracks.io; 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.ProgressIndicator; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; @@ -25,7 +24,7 @@ import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.io.mymaps.MapsFacade; import com.google.android.apps.mytracks.stats.DoubleBuffer; import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.UnitConversions; import com.google.android.maps.mytracks.R; @@ -117,7 +116,7 @@ public class SendToMyMaps implements Runnable { boolean isNewMap = mapId.equals(NEW_MAP_ID); if (isNewMap) { SharedPreferences preferences = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); boolean mapPublic = true; if (preferences != null) { mapPublic = preferences.getBoolean( @@ -196,7 +195,7 @@ public class SendToMyMaps implements Runnable { private boolean uploadAllTrackPoints( final Track track, String originalDescription) { SharedPreferences preferences = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); boolean metricUnits = true; if (preferences != null) { metricUnits = @@ -249,7 +248,7 @@ public class SendToMyMaps implements Runnable { } // Add to the elevation profile. - if (loc != null && MyTracksUtils.isValidLocation(loc)) { + if (loc != null && LocationUtils.isValidLocation(loc)) { // All points go into the smoothing buffer... elevationBuffer.setNext(metricUnits ? loc.getAltitude() : loc.getAltitude() * UnitConversions.M_TO_FT); @@ -431,11 +430,11 @@ public class SendToMyMaps implements Runnable { * Decimate to 2 meter precision. Mapshop doesn't like too many * points: */ - MyTracksUtils.decimate(segment, 2.0); + LocationUtils.decimate(segment, 2.0); /* It the track still has > 500 points, split it in pieces: */ if (segment.getLocations().size() > 500) { - splitTracks.addAll(MyTracksUtils.split(segment, 500)); + splitTracks.addAll(LocationUtils.split(segment, 500)); } else if (segment.getLocations().size() >= 2) { splitTracks.add(segment); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java index cde8c1954..1fbcf9485 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.io.backup; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.content.TrackPointsColumns; import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.content.WaypointsColumns; @@ -201,7 +200,7 @@ class ExternalFileBackup { // Dump preferences SharedPreferences preferences = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); preferencesHelper.exportPreferences(preferences, outWriter); } catch (IOException e) { // We tried to delete the partially created file, but do nothing @@ -253,7 +252,7 @@ class ExternalFileBackup { // Restore preferences SharedPreferences preferences = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); preferencesHelper.importPreferences(reader, preferences); } finally { compressedStream.close(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/MyTracksBackupAgent.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/MyTracksBackupAgent.java index 729e0125f..4438ca47f 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/MyTracksBackupAgent.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/MyTracksBackupAgent.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.io.backup; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -42,7 +41,7 @@ public class MyTracksBackupAgent extends BackupAgent { ParcelFileDescriptor newState) throws IOException { Log.i(Constants.TAG, "Performing backup"); SharedPreferences preferences = this.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); backupPreferences(data, preferences); Log.i(Constants.TAG, "Backup complete"); @@ -92,7 +91,7 @@ public class MyTracksBackupAgent extends BackupAgent { } SharedPreferences preferences = this.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); PreferenceBackupHelper importer = createPreferenceBackupHelper(); importer.importPreferences(dataBuffer, preferences); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/CsvTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java similarity index 97% rename from MyTracks/src/com/google/android/apps/mytracks/io/CsvTrackWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java index 4cfcdbe4f..0cf49dfe8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/CsvTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/CsvTrackWriter.java @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import android.location.Location; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java similarity index 99% rename from MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java index f92385038..66afa09e1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxImporter.java @@ -13,13 +13,13 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; import android.location.Location; @@ -362,7 +362,7 @@ public class GpxImporter extends DefaultHandler { * @throws SAXException - thrown if track point is invalid */ private void onTrackPointElementEnd() throws SAXException { - if (MyTracksUtils.isValidLocation(location)) { + if (LocationUtils.isValidLocation(location)) { if (statsBuilder == null) { // first point did not have a time, start stats builder without it statsBuilder = new TripStatisticsBuilder(0); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java similarity index 97% rename from MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java index 86c27bf9e..88fde6c3c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/GpxTrackWriter.java @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.util.StringUtils; import android.location.Location; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/KmlTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/io/KmlTrackWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java index 8ee29c1ee..2b6d980b6 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/KmlTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/KmlTrackWriter.java @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.util.StringUtils; import android.content.Context; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java similarity index 94% rename from MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java index cf965b4c6..eaf45a7cc 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java @@ -13,15 +13,15 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; +import com.google.android.apps.mytracks.util.SystemUtils; import android.content.Context; import android.location.Location; @@ -236,7 +236,7 @@ public class TcxTrackWriter implements TrackFormatWriter { // it. The TCX schema tightly defined the Version tag, so we can't put it // there. They've similarly constrained the PartNumber tag, so it can't go // there either. - pw.format("My Tracks %s by Google\n", MyTracksUtils.getMyTracksVersion(context)); + pw.format("My Tracks %s by Google\n", SystemUtils.getMyTracksVersion(context)); pw.println(""); @@ -246,11 +246,11 @@ public class TcxTrackWriter implements TrackFormatWriter { // signify that this build is newer than the one associated with the // version code given in VersionMajor. pw.println(""); - pw.format("%d\n", MyTracksUtils.getMyTracksVersion(context)); - pw.format("%d\n", MyTracksUtils.isRelease(context) ? 0 : 1); + pw.format("%s\n", SystemUtils.getMyTracksVersion(context)); + pw.format("%d\n", SystemUtils.isRelease(context) ? 0 : 1); pw.println(""); - pw.format("%s\n", MyTracksUtils.isRelease(context) ? TCX_TYPE_RELEASE + pw.format("%s\n", SystemUtils.isRelease(context) ? TCX_TYPE_RELEASE : TCX_TYPE_INTERNAL); pw.println(""); pw.format("%s\n", Locale.getDefault().getLanguage()); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java similarity index 97% rename from MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java index 21621225d..6f590342b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TempFileCleaner.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TempFileCleaner.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import android.os.Environment; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TrackFormatWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/io/TrackFormatWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java index c5ac02514..11f93079c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackFormatWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackFormatWriter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java index c18788572..e4ee0589c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.MyTracksLocation; @@ -22,7 +22,7 @@ import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator; import com.google.android.apps.mytracks.util.FileUtils; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; @@ -302,7 +302,7 @@ public class TrackWriter { while (it.hasNext()) { Location loc = it.next(); - boolean isValid = MyTracksUtils.isValidLocation(loc); + boolean isValid = LocationUtils.isValidLocation(loc); boolean validSegment = isValid && isLastValid; if (!wroteFirst && validSegment) { // Found the first two consecutive points which are valid diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.java rename to MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java index 785454b0b..9a0692f88 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TrackWriterFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java @@ -14,7 +14,7 @@ * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java index 9296de275..16df9c5e3 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendDialog.java @@ -15,9 +15,9 @@ */ package com.google.android.apps.mytracks.io.sendtogoogle; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.DialogManager; import com.google.android.apps.mytracks.MyTracks; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.maps.mytracks.R; import android.app.Dialog; @@ -107,7 +107,7 @@ public class SendDialog extends Dialog { R.id.sendtogoogle_send_stats_and_points); SharedPreferences prefs = - getContext().getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0); if (prefs != null) { sendToMyMapsCheckBox.setChecked( prefs.getBoolean( @@ -138,7 +138,7 @@ public class SendDialog extends Dialog { @Override protected void onStop() { SharedPreferences prefs = - getContext().getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0); if (prefs != null) { Editor editor = prefs.edit(); if (editor != null) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java index 79d2f5328..a83658854 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/DefaultTrackNameFactory.java @@ -15,7 +15,7 @@ */ package com.google.android.apps.mytracks.services; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.maps.mytracks.R; import android.content.Context; @@ -58,7 +58,7 @@ class DefaultTrackNameFactory { /** Determines whether the preferences allow a timestamp-based track name */ protected boolean useTimestampTrackName() { SharedPreferences prefs = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); return prefs.getBoolean( context.getString(R.string.timestamp_track_name_key), true); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java index 2d03b2afb..91a3dc210 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.services; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.maps.mytracks.R; import android.content.SharedPreferences; @@ -45,7 +44,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { public PreferenceManager(TrackRecordingService service) { this.service = service; this.sharedPreferences = service.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); if (sharedPreferences == null) { Log.w(Constants.TAG, "TrackRecordingService: Couldn't get shared preferences."); @@ -97,7 +96,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { service.setMinRecordingDistance( sharedPreferences.getInt( minRecordingDistanceKey, - MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE)); + Constants.DEFAULT_MIN_RECORDING_DISTANCE)); Log.d(Constants.TAG, "TrackRecordingService: minRecordingDistance = " + service.getMinRecordingDistance()); @@ -105,12 +104,12 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { if (key == null || key.equals(maxRecordingDistanceKey)) { service.setMaxRecordingDistance(sharedPreferences.getInt( maxRecordingDistanceKey, - MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE)); + Constants.DEFAULT_MAX_RECORDING_DISTANCE)); } if (key == null || key.equals(minRecordingIntervalKey)) { int minRecordingInterval = sharedPreferences.getInt( minRecordingIntervalKey, - MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL); + Constants.DEFAULT_MIN_RECORDING_INTERVAL); switch (minRecordingInterval) { case -2: // Battery Miser @@ -136,7 +135,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { if (key == null || key.equals(minRequiredAccuracyKey)) { service.setMinRequiredAccuracy(sharedPreferences.getInt( minRequiredAccuracyKey, - MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY)); + Constants.DEFAULT_MIN_REQUIRED_ACCURACY)); } if (key == null || key.equals(announcementFrequencyKey)) { service.setAnnouncementFrequency( @@ -145,7 +144,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener { if (key == null || key.equals(autoResumeTrackTimeoutKey)) { service.setAutoResumeTrackTimeout(sharedPreferences.getInt( autoResumeTrackTimeoutKey, - MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT)); + Constants.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT)); } if (key == null || key.equals(recordingTrackKey)) { long recordingTrackId = sharedPreferences.getLong(recordingTrackKey, -1); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java index 8f7f1cc52..4f27556d1 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/StatusAnnouncerTask.java @@ -19,7 +19,6 @@ package com.google.android.apps.mytracks.services; 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.stats.TripStatistics; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.apps.mytracks.util.UnitConversions; @@ -176,7 +175,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/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index 57815d323..fa15577d4 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -20,7 +20,6 @@ import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME import com.google.android.apps.mytracks.MyTracks; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Sensor; @@ -36,7 +35,7 @@ import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.stats.TripStatisticsBuilder; import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.ApiPlatformAdapter; -import com.google.android.apps.mytracks.util.MyTracksUtils; +import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.maps.mytracks.R; @@ -81,13 +80,13 @@ public class TrackRecordingService extends Service implements LocationListener { private WakeLock wakeLock; private int minRecordingDistance = - MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE; + Constants.DEFAULT_MIN_RECORDING_DISTANCE; private int maxRecordingDistance = - MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE; + Constants.DEFAULT_MAX_RECORDING_DISTANCE; private int minRequiredAccuracy = - MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY; + Constants.DEFAULT_MIN_REQUIRED_ACCURACY; private int autoResumeTrackTimeout = - MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT; + Constants.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT; private long recordingTrackId = -1; @@ -215,7 +214,7 @@ public class TrackRecordingService extends Service implements LocationListener { // Keep track of length along recorded track (needed when a waypoint is // inserted): - if (MyTracksUtils.isValidLocation(location)) { + if (LocationUtils.isValidLocation(location)) { if (lastValidLocation != null) { length += location.distanceTo(lastValidLocation); } @@ -453,7 +452,7 @@ public class TrackRecordingService extends Service implements LocationListener { if (cursor.moveToLast()) { do { Location location = providerUtils.createLocation(cursor); - if (MyTracksUtils.isValidLocation(location)) { + if (LocationUtils.isValidLocation(location)) { statsBuilder.addLocation(location, location.getTime()); if (lastValidLocation != null) { length += location.distanceTo(lastValidLocation); @@ -629,7 +628,7 @@ public class TrackRecordingService extends Service implements LocationListener { } private void addLocationToStats(Location location) { - if (MyTracksUtils.isValidLocation(location)) { + if (LocationUtils.isValidLocation(location)) { long now = System.currentTimeMillis(); statsBuilder.addLocation(location, now); waypointStatsBuilder.addLocation(location, now); @@ -836,7 +835,7 @@ public class TrackRecordingService extends Service implements LocationListener { // Check if we haven't exceeded the maximum number of retry attempts. SharedPreferences sharedPreferences = - getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + getSharedPreferences(Constants.SETTINGS_NAME, 0); int retries = sharedPreferences.getInt( getString(R.string.auto_resume_track_current_retry_key), 0); Log.d(TAG, @@ -1117,7 +1116,7 @@ public class TrackRecordingService extends Service implements LocationListener { // Reset the number of auto-resume retries. setAutoResumeTrackRetries( - getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0), 0); + getSharedPreferences(Constants.SETTINGS_NAME, 0), 0); // Persist the current recording track. prefManager.setRecordingTrack(recordingTrackId); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java index b18c9137c..f9c2d6c4a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java @@ -16,7 +16,8 @@ package com.google.android.apps.mytracks.services.sensors; import static com.google.android.apps.mytracks.Constants.TAG; -import com.google.android.apps.mytracks.MyTracksSettings; + +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.services.sensors.ant.AntChannelIdMessage; import com.google.android.apps.mytracks.services.sensors.ant.AntChannelResponseMessage; @@ -58,7 +59,7 @@ public class AntDirectSensorManager extends AntSensorManager { // First read the the device id that we will be pairing with. SharedPreferences prefs = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); if (prefs != null) { deviceNumberHRM = (short) prefs.getInt(context.getString(R.string.ant_heart_rate_sensor_id_key), 0); @@ -133,7 +134,7 @@ public class AntDirectSensorManager extends AntSensorManager { Log.i(TAG, "Found device id: " + deviceNumberHRM); SharedPreferences prefs = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE); + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(context.getString(R.string.ant_heart_rate_sensor_id_key), deviceNumberHRM); editor.commit(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java index ea4ceb818..cddc0d57e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.services.sensors; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.maps.mytracks.R; @@ -71,7 +70,7 @@ public class AntSRMSensorManager extends AntSensorManager { // First read the the device id that we will be announcing. SharedPreferences prefs = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); if (prefs != null) { deviceId = (byte) prefs.getInt( context.getString(R.string.ant_srm_bridge_sensor_id_key), @@ -133,7 +132,7 @@ public class AntSRMSensorManager extends AntSensorManager { Log.i(Constants.TAG, "Found device id: " + deviceId); SharedPreferences prefs = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE); + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(context.getString(R.string.ant_srm_bridge_sensor_id_key), deviceId); editor.commit(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java index a19013a2e..41fed2e52 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.services.sensors; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.maps.mytracks.R; @@ -82,7 +81,7 @@ public class BluetoothSensorManager extends SensorManager { return; } SharedPreferences prefs = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); String address = prefs.getString(context.getString(R.string.bluetooth_sensor_key), null); if (address == null) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java index cf0d3a458..051f9949e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java @@ -16,7 +16,6 @@ package com.google.android.apps.mytracks.services.sensors; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; import com.google.android.maps.mytracks.R; import android.content.Context; @@ -40,7 +39,7 @@ public class SensorManagerFactory { */ public static SensorManager getSensorManager(Context context) { SharedPreferences prefs = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); if (prefs == null) { return null; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java index 5ca4c3d4f..ddad2394a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java +++ b/MyTracks/src/com/google/android/apps/mytracks/stats/TripStatisticsBuilder.java @@ -19,7 +19,7 @@ package com.google.android.apps.mytracks.stats; import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.apps.mytracks.Constants; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.SettingsActivity; import android.location.Location; import android.util.Log; @@ -93,7 +93,7 @@ public class TripStatisticsBuilder { private long totalLocations = 0; private int minRecordingDistance = - MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE; + Constants.DEFAULT_MIN_RECORDING_DISTANCE; /** * Creates a new trip starting at the given time. diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ChartURLGenerator.java b/MyTracks/src/com/google/android/apps/mytracks/util/ChartURLGenerator.java index 3d55dc66b..5c0336192 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ChartURLGenerator.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ChartURLGenerator.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.util; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.maps.mytracks.R; @@ -50,7 +50,7 @@ public class ChartURLGenerator { public static String getChartUrl(Vector distances, Vector elevations, Track track, Context context) { SharedPreferences preferences = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); boolean metricUnits = true; if (preferences != null) { metricUnits = preferences.getBoolean( diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/LocationUtils.java similarity index 74% rename from MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java rename to MyTracks/src/com/google/android/apps/mytracks/util/LocationUtils.java index 4cadca845..1786cf3a6 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/LocationUtils.java @@ -21,15 +21,7 @@ import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.maps.GeoPoint; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.Signature; import android.location.Location; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; import android.util.Log; import java.util.ArrayList; @@ -41,9 +33,7 @@ import java.util.Stack; * * @author Leif Hendrik Wilden */ -public class MyTracksUtils { - private static final int RELEASE_SIGNATURE_HASHCODE = -1855564782; - +public class LocationUtils { /** * Computes the distance on the two sphere between the point c0 and the line * segment c1 to c2. @@ -117,7 +107,7 @@ public class MyTracksUtils { current = stack.pop(); maxDist = 0; for (idx = current[0] + 1; idx < current[1]; ++idx) { - dist = MyTracksUtils.distance( + dist = LocationUtils.distance( locations.get(idx), locations.get(current[0]), locations.get(current[1])); @@ -255,80 +245,9 @@ public class MyTracksUtils { (int) (location.getLongitude() * 1E6)); } - /** - * Returns whether or not this is a release build. - */ - public static boolean isRelease(Context context) { - try { - Signature [] sigs = context.getPackageManager().getPackageInfo( - context.getPackageName(), PackageManager.GET_SIGNATURES).signatures; - for (Signature sig : sigs) { - if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) { - return true; - } - } - } catch (NameNotFoundException e) { - Log.e(Constants.TAG, "Unable to get signatures", e); - } - return false; - } - - /** - * Get the My Tracks version from the manifest. - * - * @return the version, or an empty string in case of failure. - */ - public static String getMyTracksVersion(Context context) { - try { - PackageInfo pi = context.getPackageManager().getPackageInfo( - "com.google.android.maps.mytracks", - PackageManager.GET_META_DATA); - return pi.versionName; - } catch (NameNotFoundException e) { - Log.w(Constants.TAG, "Failed to get version info.", e); - return ""; - } - } - - /** - * Tries to acquire a partial wake lock if not already acquired. Logs errors - * and gives up trying in case the wake lock cannot be acquired. - */ - public static WakeLock acquireWakeLock(Activity activity, WakeLock wakeLock) { - Log.i(Constants.TAG, "MyTracksUtils: Acquiring wake lock."); - try { - PowerManager pm = (PowerManager) activity - .getSystemService(Context.POWER_SERVICE); - if (pm == null) { - Log.e(Constants.TAG, "MyTracksUtils: Power manager not found!"); - return wakeLock; - } - if (wakeLock == null) { - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - Constants.TAG); - if (wakeLock == null) { - Log.e(Constants.TAG, - "MyTracksUtils: Could not create wake lock (null)."); - } - return wakeLock; - } - if (!wakeLock.isHeld()) { - wakeLock.acquire(); - if (!wakeLock.isHeld()) { - Log.e(Constants.TAG, - "MyTracksUtils: Could not acquire wake lock."); - } - } - } catch (RuntimeException e) { - Log.e(Constants.TAG, - "MyTracksUtils: Caught unexpected exception: " + e.getMessage(), e); - } - return wakeLock; - } - /** * This is a utility class w/ only static members. */ - protected MyTracksUtils() { + private LocationUtils() { } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java index b6b8baace..05d87c61d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java @@ -15,7 +15,7 @@ */ package com.google.android.apps.mytracks.util; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.DescriptionGenerator; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; @@ -245,7 +245,7 @@ public class StringUtils implements DescriptionGenerator { Vector elevations) { boolean displaySpeed = true; SharedPreferences preferences = - context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0); + context.getSharedPreferences(Constants.SETTINGS_NAME, 0); if (preferences != null) { displaySpeed = preferences.getBoolean(context.getString(R.string.report_speed_key), true); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/SystemUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/SystemUtils.java new file mode 100644 index 000000000..c45c88308 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/SystemUtils.java @@ -0,0 +1,111 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import com.google.android.apps.mytracks.Constants; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +/** + * Utility class for acessing basic Android functionality. + * + * @author Rodrigo Damazio + */ +public class SystemUtils { + + private static final int RELEASE_SIGNATURE_HASHCODE = -1855564782; + + /** + * Returns whether or not this is a release build. + */ + public static boolean isRelease(Context context) { + try { + Signature [] sigs = context.getPackageManager().getPackageInfo( + context.getPackageName(), PackageManager.GET_SIGNATURES).signatures; + for (Signature sig : sigs) { + if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) { + return true; + } + } + } catch (NameNotFoundException e) { + Log.e(Constants.TAG, "Unable to get signatures", e); + } + return false; + } + + /** + * Get the My Tracks version from the manifest. + * + * @return the version, or an empty string in case of failure. + */ + public static String getMyTracksVersion(Context context) { + try { + PackageInfo pi = context.getPackageManager().getPackageInfo( + "com.google.android.maps.mytracks", + PackageManager.GET_META_DATA); + return pi.versionName; + } catch (NameNotFoundException e) { + Log.w(Constants.TAG, "Failed to get version info.", e); + return ""; + } + } + + /** + * Tries to acquire a partial wake lock if not already acquired. Logs errors + * and gives up trying in case the wake lock cannot be acquired. + */ + public static WakeLock acquireWakeLock(Activity activity, WakeLock wakeLock) { + Log.i(Constants.TAG, "LocationUtils: Acquiring wake lock."); + try { + PowerManager pm = (PowerManager) activity + .getSystemService(Context.POWER_SERVICE); + if (pm == null) { + Log.e(Constants.TAG, "LocationUtils: Power manager not found!"); + return wakeLock; + } + if (wakeLock == null) { + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + Constants.TAG); + if (wakeLock == null) { + Log.e(Constants.TAG, + "LocationUtils: Could not create wake lock (null)."); + } + return wakeLock; + } + if (!wakeLock.isHeld()) { + wakeLock.acquire(); + if (!wakeLock.isHeld()) { + Log.e(Constants.TAG, + "LocationUtils: Could not acquire wake lock."); + } + } + } catch (RuntimeException e) { + Log.e(Constants.TAG, + "LocationUtils: Caught unexpected exception: " + e.getMessage(), e); + } + return wakeLock; + } + + private SystemUtils() {} +} \ No newline at end of file diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java index 4566225d5..2aaa1c4f1 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java @@ -36,8 +36,8 @@ public class MyTracksLocation extends Location { this.sensorDataSet = sd; } - public MyTracksLocation(String string) { - super(string); + public MyTracksLocation(String provider) { + super(provider); } public Sensor.SensorDataSet getSensorDataSet() { diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java index 586228b25..a9506f1fc 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java @@ -301,7 +301,7 @@ public interface MyTracksProviderUtils { * @return a new waypoint object */ Waypoint createWaypoint(Cursor cursor); - + /** * A lightweight wrapper around the original {@link Cursor} with a method to clean up. */ @@ -312,13 +312,14 @@ public interface MyTracksProviderUtils { * @return the ID of the most recent track point ID. */ long getLocationId(); - + /** * Should be called in case the underlying iterator hasn't reached the last record. + * Calling it if it has reached the last record is a no-op. */ void close(); } - + /** * A factory for creating new {@class Location}s. */ @@ -342,6 +343,24 @@ public interface MyTracksProviderUtils { return new Location("gps"); } }; + + /** + * A location factory which uses two location instances (one for the current location, + * and one for the previous), useful when we need to keep the last location. + */ + public class DoubleBufferedLocationFactory implements LocationFactory { + private final Location locs[] = new MyTracksLocation[] { + new MyTracksLocation("gps"), + new MyTracksLocation("gps") + }; + private int lastLoc = 0; + + @Override + public Location createLocation() { + lastLoc = (lastLoc + 1) % locs.length; + return locs[lastLoc]; + } + } /** * Creates a new read-only iterator over all track points for the given track. It provides diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java index 4d4ec7d8f..64905da79 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -1024,7 +1024,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { cursor = null; } } - + @Override public void remove() { throw new UnsupportedOperationException(); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksOverlayTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java similarity index 97% rename from MyTracksTest/src/com/google/android/apps/mytracks/MyTracksOverlayTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java index 06049f4d7..3ea8d4a11 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MyTracksOverlayTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java @@ -39,17 +39,17 @@ import java.util.List; * * @author Bartlomiej Niechwiej */ -public class MyTracksOverlayTest extends AndroidTestCase { +public class MapOverlayTest extends AndroidTestCase { private Canvas canvas; private MockMyTracksOverlay myTracksOverlay; private MapView mockView; private Projection mockProjection; /** - * A mock version of {@code MyTracksOverlay} that does not use + * A mock version of {@code MapOverlay} that does not use * {@class MapView}. */ - private class MockMyTracksOverlay extends MyTracksOverlay { + private class MockMyTracksOverlay extends MapOverlay { public MockMyTracksOverlay(Context context) { super(context); } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/CsvTrackWriterTest.java similarity index 79% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/CsvTrackWriterTest.java index fa0d70deb..9fd40ab05 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/CsvTrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/CsvTrackWriterTest.java @@ -1,6 +1,6 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; /** * Tests for the CSV track exporter. diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxImporterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java similarity index 98% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/GpxImporterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java index b819f5c2a..e676aae88 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxImporterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxImporterTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import static com.google.android.testing.mocking.AndroidMock.eq; import static com.google.android.testing.mocking.AndroidMock.expect; @@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.io.file.GpxImporter; import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; import com.google.android.testing.mocking.AndroidMock; import com.google.android.testing.mocking.UsesMocks; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxTrackWriterTest.java similarity index 93% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxTrackWriterTest.java index 9601875cb..36c78a33c 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/GpxTrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/GpxTrackWriterTest.java @@ -1,5 +1,8 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; + +import com.google.android.apps.mytracks.io.file.GpxTrackWriter; +import com.google.android.apps.mytracks.io.file.TrackFormatWriter; import org.w3c.dom.Document; import org.w3c.dom.Element; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java similarity index 97% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java index e3e735f2d..1a3deee16 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/KmlTrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/KmlTrackWriterTest.java @@ -1,7 +1,8 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.io.file.KmlTrackWriter; import com.google.android.apps.mytracks.util.StringUtils; import android.location.Location; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java similarity index 94% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java index e07f1ae55..380ca972f 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TcxTrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TcxTrackWriterTest.java @@ -1,8 +1,10 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.Sensor; +import com.google.android.apps.mytracks.io.file.TcxTrackWriter; +import com.google.android.apps.mytracks.io.file.TrackFormatWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java similarity index 96% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java index 25d600cbe..cea67b571 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TempFileCleanerTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TempFileCleanerTest.java @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import static com.google.android.testing.mocking.AndroidMock.expect; + +import com.google.android.apps.mytracks.io.file.TempFileCleaner; import com.google.android.testing.mocking.AndroidMock; import com.google.android.testing.mocking.UsesMocks; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java similarity index 98% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java index a4827fecd..8c265bc26 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackFormatWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackFormatWriterTest.java @@ -1,10 +1,11 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.io.file.TrackFormatWriter; import android.test.AndroidTestCase; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java similarity index 98% rename from MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java index c80013a68..c623d9a78 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java @@ -1,6 +1,6 @@ // Copyright 2010 Google Inc. All Rights Reserved. -package com.google.android.apps.mytracks.io; +package com.google.android.apps.mytracks.io.file; import static org.easymock.EasyMock.expect; @@ -9,6 +9,8 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.io.file.TrackFormatWriter; +import com.google.android.apps.mytracks.io.file.TrackWriter; import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext; import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java index b2d2f21f8..0f4c13ca8 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java @@ -17,7 +17,7 @@ package com.google.android.apps.mytracks.services; import static com.google.android.apps.mytracks.Constants.RESUME_TRACK_EXTRA_NAME; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.MyTracksProvider; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; @@ -152,7 +152,7 @@ public class TrackRecordingServiceTest providerUtils = MyTracksProviderUtils.Factory.get(context); sharedPreferences = context.getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); // Let's use default values. sharedPreferences.edit().clear().commit(); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java index ea0e482ae..6cff03544 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.services.sensors; import com.dsi.ant.AntMesg; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.maps.mytracks.R; @@ -31,7 +31,7 @@ public class AntDirectSensorManagerTest extends AndroidTestCase { public void setUp() { sharedPreferences = getContext().getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); // Let's use default values. sharedPreferences.edit().clear().commit(); manager = new AntDirectSensorManager(getContext()); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java index 3951aa3c1..b25c9a9f2 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java @@ -1,6 +1,6 @@ package com.google.android.apps.mytracks.services.sensors; -import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.Constants; import com.google.android.maps.mytracks.R; import android.content.SharedPreferences; @@ -16,7 +16,7 @@ public class SensorManagerFactoryTest extends AndroidTestCase { super.setUp(); sharedPreferences = getContext().getSharedPreferences( - MyTracksSettings.SETTINGS_NAME, 0); + Constants.SETTINGS_NAME, 0); // Let's use default values. sharedPreferences.edit().clear().commit(); }