diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 1545eb1c9..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/res/values-es/strings.xml b/MyTracks/res/values-es/strings.xml index f9e786542..7996ad1d8 100644 --- a/MyTracks/res/values-es/strings.xml +++ b/MyTracks/res/values-es/strings.xml @@ -30,18 +30,18 @@ limitations under the License. Último segmento - Ningún administrador de ubicación fue hallado en este aparato! - Ningún proveedor GPS de ubicación fue hallado en este aparato. - Hubo un error al mandar su recorrido a Google Fusion Tables. Por favor inténtelo de nuevo más tarde. - Hubo un error al mandar su recorrido a Google Docs. Por favor inténtelo de nuevo más tarde. - No pude empezar a grabar. Por favor inténtelo de nuevo. - No pude dejar de grabar. Por favor inténtelo de nuevo. + Ningún administrador de ubicación fue hallado en este dispositivo! + Ningún proveedor de ubicación GPS fue hallado en este dispositivo. + Se produjo un error al enviar su recorrido a Google Fusion Tables. + Se produjo un error al enviar su recorrido a Google Docs. + No se puede iniciar la grabación. Inténtelo de nuevo. + No se puede detener la grabación. Inténtelo de nuevo. No pude leer del archivo. El archivo no tiene un format GPX válido. - Error. Por favor inténtelo de nuevo. + A ocurrido un error. Por favor, inténtelo de nuevo. El recorrido no existe. El archivo es demasiado grande (tiene que ser menor a 500 KB). Abortando. - No pude insertar la señal. La ubicación no esta disponible. + No se puede insertar el marcador. La ubicación no esta disponible. Esperando una ubicación mas exacta del GPS. Asegúrese que el cielo sea visible. 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/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/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java similarity index 100% rename from MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java rename to MyTracks/src/com/google/android/apps/mytracks/MapActivity.java 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 ebcabae35..451e6e22b 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.TrackRecordingService; import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; 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/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 f7d106db5..2471001eb 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/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 c9cbcb3f9..3f5b666d5 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/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index 2132a90e0..88abfeee6 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; @@ -39,7 +38,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; @@ -84,13 +83,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; @@ -218,7 +217,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); } @@ -456,7 +455,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); @@ -633,7 +632,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); @@ -841,7 +840,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, @@ -1123,7 +1122,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(); }