diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java index 2184c1cf5..db12a49cd 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java @@ -70,8 +70,9 @@ import android.widget.Toast; * @author Leif Hendrik Wilden */ public class MyTracksMap extends MapActivity - implements LocationListener, SensorEventListener, View.OnTouchListener, - View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener { + implements View.OnTouchListener, View.OnClickListener, + SharedPreferences.OnSharedPreferenceChangeListener { + private static final int TRACKPOINT_BUFFER_SIZE = 1024; // Saved instance state keys: // --------------------------- @@ -82,9 +83,9 @@ public class MyTracksMap extends MapActivity public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix"; /** - * The currently selected track (or null if nothing selected). + * The ID of the currently selected track (or -1 if nothing selected). */ - private Track selectedTrack; + private long selectedTrackId = -1; /** * The id of the currently recording track. @@ -97,13 +98,19 @@ public class MyTracksMap extends MapActivity */ private boolean keepMyLocationVisible; + /** + * Id of the first location that was seen when reading tracks from the + * provider. + */ + private long firstSeenLocationId = -1; + /** * Id of the last location that was seen when reading tracks from the * provider. This is used to determine which locations are new compared to the * last time the mapOverlay was updated. */ private long lastSeenLocationId = -1; - + /** * Magnetic variation. */ @@ -130,19 +137,13 @@ public class MyTracksMap extends MapActivity * A thread with a looper. Post to updateTrackHandler to execute Runnables on * this thread. */ - private final HandlerThread updateTrackThread = - new HandlerThread("updateTrackThread"); + private HandlerThread updateTrackThread; /** Handler for updateTrackThread */ private Handler updateTrackHandler; private MyTracksProviderUtils providerUtils; - /** - * This value is used to decide how many points to display. - */ - private int samplingFrequency = 1; - /** * A runnable that updates the track from the provider (looking for points * added after "lastSeenLocationId". @@ -150,7 +151,7 @@ public class MyTracksMap extends MapActivity private final Runnable updateTrackRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } readAllNewTrackPoints(); @@ -163,11 +164,12 @@ public class MyTracksMap extends MapActivity private Runnable restoreTrackRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } mapOverlay.clearPoints(); - lastSeenLocationId = selectedTrack.getStartId(); + firstSeenLocationId = -1; + lastSeenLocationId = -1; readAllNewTrackPoints(); } }; @@ -178,7 +180,7 @@ public class MyTracksMap extends MapActivity private final Runnable restoreWaypointsRunnable = new Runnable() { @Override public void run() { - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } @@ -188,7 +190,7 @@ public class MyTracksMap extends MapActivity // We will silently drop extra waypoints to make the app responsive. // TODO: Try to only load the waypoints in the view port. cursor = providerUtils.getWaypointsCursor( - selectedTrack.getId(), 0, + selectedTrackId, 0, MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS); if (cursor != null) { if (cursor.moveToFirst()) { @@ -219,10 +221,9 @@ public class MyTracksMap extends MapActivity public void run() { uiHandler.post(new Runnable() { public void run() { - showTrack(selectedTrack); - mapOverlay.setSelectedTrack(selectedTrack); - mapOverlay.setShowEndMarker(selectedTrack == null || - selectedTrack.getId() != recordingTrackId); + showTrack(selectedTrackId); + mapOverlay.setTrackDrawingEnabled(isATrackSelected()); + mapOverlay.setShowEndMarker(!isRecordingSelected()); mapView.invalidate(); busyPane.setVisibility(View.GONE); updateOptionsButton(); @@ -316,6 +317,7 @@ public class MyTracksMap extends MapActivity locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + updateTrackThread = new HandlerThread("updateTrackThread"); updateTrackThread.start(); updateTrackHandler = new Handler(updateTrackThread.getLooper()); @@ -331,8 +333,7 @@ public class MyTracksMap extends MapActivity // No track is being recorded. We should not be here. return; } - if (selectedTrack == null - || selectedTrack.getId() != recordingTrackId) { + if (!isRecordingSelected()) { // No track, or one other than the recording track is selected, // don't bother. return; @@ -348,7 +349,7 @@ public class MyTracksMap extends MapActivity public void onChange(boolean selfChange) { Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange waypoints"); - if (selectedTrack == null) { + if (!isATrackSelected()) { return; } updateTrackHandler.post(restoreWaypointsRunnable); @@ -375,6 +376,21 @@ public class MyTracksMap extends MapActivity } } + /** + * Returns whether there's a track currently selected for display. + */ + private boolean isATrackSelected() { + return selectedTrackId >= 0; + } + + /** + * Returns whether we're currently recording the same track that's selected + * for display. + */ + private boolean isRecordingSelected() { + return isATrackSelected() && selectedTrackId == recordingTrackId; + } + protected void setupZoomControls() { mapView.setBuiltInZoomControls(true); } @@ -384,6 +400,9 @@ public class MyTracksMap extends MapActivity // Final cleanup before activity is destroyed. // May not be called at all in some situations. Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy"); + + updateTrackThread.quit(); + super.onDestroy(); } @@ -449,10 +468,9 @@ public class MyTracksMap extends MapActivity // While this activity was paused the user may have deleted the selected // track. In that case the map overlay needs to be cleared: - Track track = mapOverlay.getSelectedTrack(); - if (track != null && !providerUtils.trackExists(track.getId())) { + if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) { // The recording track must have been deleted meanwhile. - mapOverlay.setSelectedTrack(null); + mapOverlay.setTrackDrawingEnabled(false); mapView.invalidate(); } } @@ -514,10 +532,10 @@ public class MyTracksMap extends MapActivity + gpsProvider.getName()); } locationManager.requestLocationUpdates(gpsProvider.getName(), - 0 /*minTime*/, 0 /*minDist*/, this); + 0 /*minTime*/, 0 /*minDist*/, locationListener); try { locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - 1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this); + 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. @@ -535,7 +553,7 @@ public class MyTracksMap extends MapActivity Log.d(MyTracksConstants.TAG, "MyTracksMap: Now registering sensor listeners."); sensorManager.registerListener( - this, compass, SensorManager.SENSOR_DELAY_UI); + sensorListener, compass, SensorManager.SENSOR_DELAY_UI); } /** @@ -545,12 +563,12 @@ public class MyTracksMap extends MapActivity if (locationManager != null) { Log.d(MyTracksConstants.TAG, "MyTracksMap: Now unregistering location listeners."); - locationManager.removeUpdates(this); + locationManager.removeUpdates(locationListener); } if (sensorManager != null) { Log.d(MyTracksConstants.TAG, "MyTracksMap: Now unregistering sensor listeners."); - sensorManager.unregisterListener(this); + sensorManager.unregisterListener(sensorListener); } } @@ -579,7 +597,7 @@ public class MyTracksMap extends MapActivity */ private void updateOptionsButton() { optionsBtn.setVisibility( - selectedTrack != null ? View.VISIBLE : View.INVISIBLE); + isATrackSelected() ? View.VISIBLE : View.INVISIBLE); } /** @@ -632,12 +650,18 @@ public class MyTracksMap extends MapActivity /** * Zooms and pans the map so that the given track is visible. * - * @param track a given track + * @param trackId a given track ID */ - public void showTrack(Track track) { - if (track == null || mapView == null || track.getNumberOfPoints() < 2) { + public void showTrack(long trackId) { + if (mapView == null) { return; } + + Track track = providerUtils.getTrack(trackId); + if (track == null || track.getNumberOfPoints() < 2) { + return; + } + TripStatistics stats = track.getStatistics(); int bottom = stats.getBottom(); int left = stats.getLeft(); @@ -680,24 +704,30 @@ public class MyTracksMap extends MapActivity * @param trackId a given track id */ public void setSelectedTrack(final long trackId) { - if (selectedTrack != null && selectedTrack.getId() == trackId) { + if (selectedTrackId == trackId) { // Selected track did not change, nothing to do. - mapOverlay.setSelectedTrack(selectedTrack); + mapOverlay.setTrackDrawingEnabled(isATrackSelected()); mapView.invalidate(); updateOptionsButton(); return; } + if (trackId < 0) { // Remove selection. - selectedTrack = null; - mapOverlay.setSelectedTrack(null); + selectedTrackId = -1; + mapOverlay.setTrackDrawingEnabled(false); mapOverlay.clearWaypoints(); updateOptionsButton(); mapView.invalidate(); return; } + busyPane.setVisibility(View.VISIBLE); - selectedTrack = providerUtils.getTrack(trackId); + selectedTrackId = trackId; + loadSelectedTrack(); + } + + private void loadSelectedTrack() { updateTrackHandler.post(restoreTrackRunnable); updateTrackHandler.post(restoreWaypointsRunnable); updateTrackHandler.post(setSelectedTrackRunnable); @@ -753,8 +783,7 @@ public class MyTracksMap extends MapActivity menu.setHeaderTitle(R.string.tracklist_this_track); menu.add(0, MyTracksConstants.MENU_EDIT, 0, R.string.tracklist_edit_track); - if (!MyTracks.getInstance().isRecording() || (selectedTrack != null - && selectedTrack.getId() != recordingTrackId)) { + if (!MyTracks.getInstance().isRecording() || !isRecordingSelected()) { menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0, R.string.tracklist_send_to_google); SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0, @@ -791,7 +820,7 @@ public class MyTracksMap extends MapActivity @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (!super.onMenuItemSelected(featureId, item)) { - if (selectedTrack != null) { + if (isATrackSelected()) { MyTracks.getInstance().onActivityResult( MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK, new Intent()); @@ -852,8 +881,7 @@ public class MyTracksMap extends MapActivity public void onClick(View v) { if (v == messagePane) { launchMyLocationSettings(); - } - if (v == optionsBtn) { + } else if (v == optionsBtn) { optionsBtn.performLongClick(); } } @@ -895,9 +923,8 @@ public class MyTracksMap extends MapActivity sharedPreferences.getLong( getString(R.string.recording_track_key), -1); - if (selectedTrack != null) { - mapOverlay.setShowEndMarker( - selectedTrack.getId() != recordingTrackId); + if (isATrackSelected()) { + mapOverlay.setShowEndMarker(!isRecordingSelected()); mapView.postInvalidate(); } } @@ -906,136 +933,139 @@ public class MyTracksMap extends MapActivity } } - @Override - public void onProviderEnabled(String provider) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - messageText.setText(R.string.wait_for_fix); - } - } - - @Override - public void onProviderDisabled(String provider) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - messageText.setText(R.string.status_enable_gps); - messagePane.setVisibility(View.VISIBLE); - messagePane.setOnClickListener(this); - screen.requestLayout(); - } - } - - @Override - public void onLocationChanged(Location location) { - if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) { - // Recalculate the variation if there was a jump in location > 1km: - if (currentLocation == null - || location.distanceTo(currentLocation) > 1000) { - setVariation(location); + private final LocationListener locationListener = new LocationListener() { + @Override + public void onProviderEnabled(String provider) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + messageText.setText(R.string.wait_for_fix); } - currentLocation = location; - boolean haveGoodFixNow = - currentLocation.getAccuracy() < minRequiredAccuracy; - if (haveGoodFixNow != haveGoodFix) { - haveGoodFix = haveGoodFixNow; - messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE); + } + + @Override + public void onProviderDisabled(String provider) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + messageText.setText(R.string.status_enable_gps); + messagePane.setVisibility(View.VISIBLE); + messagePane.setOnClickListener(MyTracksMap.this); screen.requestLayout(); } - showCurrentLocation(); - } else { - Log.d(MyTracksConstants.TAG, - "MyTracksMap: Network location update received."); } - } - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { - switch (status) { - case LocationProvider.OUT_OF_SERVICE: - case LocationProvider.TEMPORARILY_UNAVAILABLE: - haveGoodFix = false; - messagePane.setVisibility(View.VISIBLE); + @Override + public void onLocationChanged(Location location) { + if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) { + // Recalculate the variation if there was a jump in location > 1km: + if (currentLocation == null + || location.distanceTo(currentLocation) > 1000) { + setVariation(location); + } + currentLocation = location; + boolean haveGoodFixNow = + currentLocation.getAccuracy() < minRequiredAccuracy; + if (haveGoodFixNow != haveGoodFix) { + haveGoodFix = haveGoodFixNow; + messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE); screen.requestLayout(); - break; + } + showCurrentLocation(); + } else { + Log.d(MyTracksConstants.TAG, + "MyTracksMap: Network location update received."); } } - } - @Override - public void onSensorChanged(SensorEvent se) { - synchronized (this) { - float magneticHeading = se.values[0]; - double heading = magneticHeading + variation; - if (mapOverlay.setHeading((float) heading)) { - mapView.invalidate(); + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + if (provider.equals(MyTracksConstants.GPS_PROVIDER)) { + switch (status) { + case LocationProvider.OUT_OF_SERVICE: + case LocationProvider.TEMPORARILY_UNAVAILABLE: + haveGoodFix = false; + messagePane.setVisibility(View.VISIBLE); + screen.requestLayout(); + break; + } } } - } + }; - @Override - public void onAccuracyChanged(Sensor s, int accuracy) { - // do nothing - } - - /** - * Set the sampling frequency from the total number of points in the track. - * - * @param track The track to read the total number of points from. - */ - private void setSamplingFrequency(Track track) { - long totalLocations = track.getStopId() - track.getStartId(); + private final SensorEventListener sensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent se) { + synchronized (this) { + float magneticHeading = se.values[0]; + double heading = magneticHeading + variation; + if (mapOverlay.setHeading((float) heading)) { + mapView.invalidate(); + } + } + } + + @Override + public void onAccuracyChanged(Sensor s, int accuracy) { + // do nothing + } + }; - // Limit the number of map points readings. - samplingFrequency = - (int) (1 + - totalLocations / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS); - Log.i(MyTracksConstants.TAG, "Sampling locations: " + samplingFrequency); - } - private void readAllNewTrackPoints() { - Cursor cursor = null; - // Refetch the track to get the latest StopId - selectedTrack = providerUtils.getTrack(selectedTrack.getId()); - long totalLocations = selectedTrack.getStopId() - - selectedTrack.getStartId(); + int numPoints = mapOverlay.getNumLocations(); + if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) { + // We're about to exceed the maximum allowed number of points, so reload + // the whole track with fewer points (the sampling frequency will be + // lower). + loadSelectedTrack(); + return; + } + + long lastStoredLocationId = + providerUtils.getLastLocationId(selectedTrackId); + int samplingFrequency = -1; + for (;;) { + Cursor cursor = null; + try { + cursor = providerUtils.getLocationsCursor(selectedTrackId, + lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false); + if (cursor == null || !cursor.moveToFirst()) { + // No (more) data + break; + } - setSamplingFrequency(selectedTrack); - int bufferSize = 1024; - int points = 0; - long tailLocationId = selectedTrack.getStopId() - 10; - Location location; - while (lastSeenLocationId < (selectedTrack.getStopId() + 10)) { - cursor = providerUtils.getLocationsCursor( - selectedTrack.getId(), lastSeenLocationId + 1, bufferSize, false); - if (cursor != null && cursor.moveToFirst()) { final int idColumnIdx = cursor.getColumnIndexOrThrow( TrackPointsColumns._ID); do { - points++; - location = providerUtils.createLocation(cursor); - lastSeenLocationId = cursor.getLong(idColumnIdx); - // Include a point if it fits one of the following criteria: - // - Has the mod for the sampling frequency. - // - Is the first point. - // - Is on of the last n points. - // => This is to make sure we draw the last points people are probably zoomed in to. - if (!MyTracksUtils.isValidLocation(location) || - points % samplingFrequency == 0 || - points == 0 || - lastSeenLocationId > tailLocationId) { - mapOverlay.addLocation(new Location(location)); + long locationId = cursor.getLong(idColumnIdx); + lastSeenLocationId = locationId; + if (firstSeenLocationId == -1) { + // This was our first point, keep its ID + firstSeenLocationId = locationId; } + if (samplingFrequency == -1) { + // Now we already have at least one point, calculate the sampling + // frequency + long numTotalPoints = lastStoredLocationId - firstSeenLocationId; + samplingFrequency = (int) (1 + + numTotalPoints / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS); + } + + Location location = providerUtils.createLocation(cursor); + + // Include a point if it fits one of the following criteria: + // - Has the mod for the sampling frequency (includes first point). + // - Is the last point and we are not recording this track. + // - The point is a segment split + if (numPoints % samplingFrequency == 0 || + (!isRecordingSelected() && locationId == lastStoredLocationId) || + !MyTracksUtils.isValidLocation(location)) { + mapOverlay.addLocation(location); + } + + numPoints++; } while (cursor.moveToNext()); - } else { - // TODO this is wrong sometimes. Should just return. - if (lastSeenLocationId >= selectedTrack.getStopId()) { + } finally { + if (cursor != null) { cursor.close(); - mapView.postInvalidate(); - return; } - lastSeenLocationId += bufferSize; } - cursor.close(); - cursor = null; } mapView.postInvalidate(); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java index e84a59aad..4f776802b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksOverlay.java @@ -15,10 +15,8 @@ */ package com.google.android.apps.mytracks; -import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.util.MyTracksUtils; - import com.google.android.maps.GeoPoint; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; @@ -58,7 +56,7 @@ public class MyTracksOverlay extends Overlay { private final ArrayList waypoints = new ArrayList(); private final ArrayList points = new ArrayList(1024); - private Track selectedTrack; + private boolean trackDrawingEnabled; private int lastHeading = 0; private Location myLocation; private boolean showEndMarker = true; @@ -122,14 +120,6 @@ public class MyTracksOverlay extends Overlay { errorCirclePaint.setAntiAlias(true); } - public void setSelectedTrack(Track track) { - selectedTrack = track; - } - - public Track getSelectedTrack() { - return selectedTrack; - } - /** * Add a location to the map overlay. * @@ -145,6 +135,10 @@ public class MyTracksOverlay extends Overlay { waypoints.add(wpt); } + public int getNumLocations() { + return points.size(); + } + public void clearWaypoints() { waypoints.clear(); } @@ -153,21 +147,33 @@ public class MyTracksOverlay extends Overlay { points.clear(); } + public void setTrackDrawingEnabled(boolean trackDrawingEnabled) { + this.trackDrawingEnabled = trackDrawingEnabled; + } + public void setShowEndMarker(boolean showEndMarker) { this.showEndMarker = showEndMarker; } @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { - if (shadow) { return; } - // Draw the selected track: - drawTrack(canvas, mapView, selectedTrack, true); + if (trackDrawingEnabled) { + // Draw the selected track: + drawTrack(canvas, mapView); - // Draw the waypoints: + // Draw the waypoints: + drawWaypoints(canvas, mapView); + } + + // Draw the current location + drawMyLocation(canvas, mapView); + } + + private void drawWaypoints(Canvas canvas, MapView mapView) { ArrayList currentWaypoints = waypoints; for (int i = 1; i < currentWaypoints.size(); i++) { Waypoint wpt = currentWaypoints.get(i); @@ -190,11 +196,14 @@ public class MyTracksOverlay extends Overlay { } canvas.restore(); } + } + private void drawMyLocation(Canvas canvas, MapView mapView) { // Draw the arrow icon: if (myLocation == null) { return; } + GeoPoint geoPoint = new GeoPoint( (int) (myLocation.getLatitude() * 1E6), (int) (myLocation.getLongitude() * 1E6)); @@ -211,12 +220,7 @@ public class MyTracksOverlay extends Overlay { canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint); } - public void drawTrack(Canvas canvas, MapView mapView, Track track, - boolean selected) { - - if (track == null) { - return; - } + private void drawTrack(Canvas canvas, MapView mapView) { if (points.size() < 2) { return; }