diff --git a/MyTracks/.classpath b/MyTracks/.classpath index 0ca188f97..d3bb07b44 100644 --- a/MyTracks/.classpath +++ b/MyTracks/.classpath @@ -1,8 +1,8 @@ + - diff --git a/MyTracks/.project b/MyTracks/.project index cf54ef5ba..81b2d0da6 100644 --- a/MyTracks/.project +++ b/MyTracks/.project @@ -3,7 +3,7 @@ MyTracks - MyTracksLib + MyTracks diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index a584d2ae2..6898ef5a4 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -16,8 +16,8 @@ limitations under the License. --> @@ -41,6 +41,7 @@ limitations under the License. + @@ -79,9 +80,12 @@ limitations under the License. android:permissionGroup="android.permission-group.LOCATION" android:protectionLevel="dangerous" /> + @@ -130,6 +134,10 @@ limitations under the License. + + - - + android:showAsAction="ifRoom" + android:title="@string/menu_map_layer"> + + + + + + \ No newline at end of file diff --git a/MyTracks/res/values/colors.xml b/MyTracks/res/values/colors.xml index 25810692f..1e96b0266 100644 --- a/MyTracks/res/values/colors.xml +++ b/MyTracks/res/values/colors.xml @@ -16,7 +16,6 @@ limitations under the License. --> #FF000000 - #FF0000FF #FF444444 #FFFAAD2C #FFBE161B diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index ca0f1b872..482e6bfca 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -1157,11 +1157,18 @@ limitations under the License. Character limit: ~20 (1 line)"> Insert marker - - Map mode + Map + + + Map layer Play - Satellite @@ -1256,6 +1264,13 @@ limitations under the License. Character limit: ~20 (1 line)"> Stop GPS + + Terrain + Configure Widget Items - + diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java index 4f6cd3c85..44b30e677 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java @@ -556,7 +556,7 @@ public class ChartView extends View { float x = getX(getWaypointXValue(waypoint)); canvas.drawLine( x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint); - canvas.translate(x - (float) (markerWidth * Constants.WAYPOINT_X_OFFSET_PERCENTAGE), + canvas.translate(x - (float) (markerWidth * MapOverlay.WAYPOINT_X_ANCHOR), topBorder + spacer); if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) { statisticsMarker.draw(canvas); diff --git a/MyTracks/src/com/google/android/apps/mytracks/Constants.java b/MyTracks/src/com/google/android/apps/mytracks/Constants.java index 0df851792..371e26333 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/Constants.java +++ b/MyTracks/src/com/google/android/apps/mytracks/Constants.java @@ -126,10 +126,6 @@ public abstract class Constants { public static final String SETTINGS_NAME = "SettingsActivity"; - public static final double WAYPOINT_X_OFFSET_PERCENTAGE = 13 / 48.0; - - public static final double MARKER_Y_OFFSET_PERCENTAGE = 91 / 96.0; - /** * This is an abstract utility class. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java index d8f233cdd..439195966 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java @@ -19,30 +19,22 @@ package com.google.android.apps.mytracks; import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.maps.TrackPathPainter; -import com.google.android.apps.mytracks.maps.TrackPathPainterFactory; -import com.google.android.apps.mytracks.maps.TrackPathUtils; -import com.google.android.apps.mytracks.util.IntentUtils; +import com.google.android.apps.mytracks.maps.TrackPath; +import com.google.android.apps.mytracks.maps.TrackPathFactory; import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.UnitConversions; -import com.google.android.maps.GeoPoint; -import com.google.android.maps.MapView; -import com.google.android.maps.Overlay; -import com.google.android.maps.Projection; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.BitmapDescriptors; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.Model; +import com.google.android.gms.maps.model.Polyline; import com.google.android.maps.mytracks.R; -import com.google.common.annotations.VisibleForTesting; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.location.Location; import android.util.Log; @@ -56,41 +48,33 @@ import java.util.concurrent.BlockingQueue; * * @author Leif Hendrik Wilden */ -public class MapOverlay extends Overlay { +public class MapOverlay { + + public static final float WAYPOINT_X_ANCHOR = 13f / 48f; + + private static final float WAYPOINT_Y_ANCHOR = 43f / 48f; + private static final float MARKER_X_ANCHOR = 50f / 96f; + private static final float MARKER_Y_ANCHOR = 90f / 96f; + private static final int INITIAL_LOCATIONS_SIZE = 1024; private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (PreferencesUtils.getKey(context, R.string.track_color_mode_key).equals(key)) { - trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context); + if (key == null + || PreferencesUtils.getKey(context, R.string.track_color_mode_key).equals(key)) { + trackPath = TrackPathFactory.getTrackPath(context); } } }; private final Context context; + private final List locations; + private final BlockingQueue pendingLocations; private final List waypoints; - private final List points; - private final BlockingQueue pendingPoints; - private final Drawable[] arrows; - private final int arrowWidth; - private final int arrowHeight; - private final Drawable statsMarker; - private final Drawable waypointMarker; - private final Drawable startMarker; - private final Drawable endMarker; - private final int markerWidth; - private final int markerHeight; - private final Paint errorCirclePaint; - private TrackPathPainter trackPathPainter; - private boolean trackDrawingEnabled; private boolean showEndMarker = true; - private int headingIndex = 0; - private Location myLocation; - - private GeoPoint lastReferencePoint; - private Rect lastViewRect; + private TrackPath trackPath; /** * A pre-processed {@link Location} to speed up drawing. @@ -100,7 +84,7 @@ public class MapOverlay extends Overlay { public static class CachedLocation { private final boolean valid; - private final GeoPoint geoPoint; + private final LatLng latLng; private final int speed; /** @@ -108,7 +92,7 @@ public class MapOverlay extends Overlay { */ public CachedLocation() { this.valid = false; - this.geoPoint = null; + this.latLng = null; this.speed = -1; } @@ -117,7 +101,7 @@ public class MapOverlay extends Overlay { */ public CachedLocation(Location location) { this.valid = LocationUtils.isValidLocation(location); - this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null; + this.latLng = valid ? new LatLng(location.getLatitude(), location.getLongitude()) : null; this.speed = (int) Math.floor(location.getSpeed() * UnitConversions.MS_TO_KMH); } @@ -128,94 +112,52 @@ public class MapOverlay extends Overlay { return valid; } - /** - * Gets the {@link GeoPoint}. - */ - public GeoPoint getGeoPoint() { - return geoPoint; - } - /** * Gets the speed in kilometers per hour. */ public int getSpeed() { return speed; } + + /** + * Gets the LatLng. + */ + public LatLng getLatLng() { + return latLng; + } }; public MapOverlay(Context context) { this.context = context; this.waypoints = new ArrayList(); - this.points = new ArrayList(1024); - this.pendingPoints = new ArrayBlockingQueue( + this.locations = new ArrayList(INITIAL_LOCATIONS_SIZE); + this.pendingLocations = new ArrayBlockingQueue( Constants.MAX_DISPLAYED_TRACK_POINTS, true); - /* - * TODO: Use animation rather than individual resources for each arrow - * direction. - */ - final Resources resources = context.getResources(); - arrows = new Drawable[] { resources.getDrawable(R.drawable.arrow_0), - resources.getDrawable(R.drawable.arrow_20), resources.getDrawable(R.drawable.arrow_40), - resources.getDrawable(R.drawable.arrow_60), resources.getDrawable(R.drawable.arrow_80), - resources.getDrawable(R.drawable.arrow_100), resources.getDrawable(R.drawable.arrow_120), - resources.getDrawable(R.drawable.arrow_140), resources.getDrawable(R.drawable.arrow_160), - resources.getDrawable(R.drawable.arrow_180), resources.getDrawable(R.drawable.arrow_200), - resources.getDrawable(R.drawable.arrow_220), resources.getDrawable(R.drawable.arrow_240), - resources.getDrawable(R.drawable.arrow_260), resources.getDrawable(R.drawable.arrow_280), - resources.getDrawable(R.drawable.arrow_300), resources.getDrawable(R.drawable.arrow_320), - resources.getDrawable(R.drawable.arrow_340) }; - arrowWidth = arrows[headingIndex].getIntrinsicWidth(); - arrowHeight = arrows[headingIndex].getIntrinsicHeight(); - for (Drawable arrow : arrows) { - arrow.setBounds(0, 0, arrowWidth, arrowHeight); - } - - statsMarker = resources.getDrawable(R.drawable.yellow_pushpin); - markerWidth = statsMarker.getIntrinsicWidth(); - markerHeight = statsMarker.getIntrinsicHeight(); - statsMarker.setBounds(0, 0, markerWidth, markerHeight); - - waypointMarker = resources.getDrawable(R.drawable.blue_pushpin); - waypointMarker.setBounds(0, 0, markerWidth, markerHeight); - - startMarker = resources.getDrawable(R.drawable.green_dot); - startMarker.setBounds(0, 0, markerWidth, markerHeight); - - endMarker = resources.getDrawable(R.drawable.red_dot); - endMarker.setBounds(0, 0, markerWidth, markerHeight); - - errorCirclePaint = TrackPathUtils.getPaint(context, R.color.blue); - errorCirclePaint.setAlpha(127); - - trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context); - context.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE) .registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); } /** - * Add a location to the map overlay. - *

- * NOTE: This method doesn't take ownership of the given location, so it is - * safe to reuse the same location while calling this method. + * Add a location. * * @param location the location */ public void addLocation(Location location) { - // Queue up in the pendingPoints until it's merged with points. - if (!pendingPoints.offer(new CachedLocation(location))) { - Log.e(TAG, "Unable to add to pendingPoints."); + // Queue up in the pendingLocations until it's merged with locations + if (!pendingLocations.offer(new CachedLocation(location))) { + Log.e(TAG, "Unable to add to pendingLocations."); } } /** - * Adds a segment split to the map overlay. + * Adds a segment split. */ public void addSegmentSplit() { - // Queue up in the pendingPoints until it's merged with points. - if (!pendingPoints.offer(new CachedLocation())) { - Log.e(TAG, "Unable to add to pendingPoints"); + // Queue up in the pendingLocations until it's merged with locations + if (!pendingLocations.offer(new CachedLocation())) { + Log.e(TAG, "Unable to add to pendingLocations."); } } @@ -223,26 +165,20 @@ public class MapOverlay extends Overlay { * Clears the locations. */ public void clearPoints() { - synchronized (points) { - points.clear(); - pendingPoints.clear(); - trackPathPainter.clearPath(); - lastReferencePoint = null; - lastViewRect = null; + synchronized (locations) { + locations.clear(); + pendingLocations.clear(); } } /** - * Adds a waypoint to the map overlay. + * Adds a waypoint. * * @param waypoint the waypoint */ public void addWaypoint(Waypoint waypoint) { - // Note: We don't cache waypoints because it's not worth the effort. - if (waypoint != null && waypoint.getLocation() != null) { - synchronized (waypoints) { - waypoints.add(waypoint); - } + synchronized (waypoints) { + waypoints.add(waypoint); } } @@ -256,327 +192,92 @@ public class MapOverlay extends Overlay { } /** - * Sets whether to draw the track or not. + * Sets whether to show the end marker. * - * @param trackDrawingEnabled true to draw track + * @param show true to show the end marker */ - public void setTrackDrawingEnabled(boolean trackDrawingEnabled) { - this.trackDrawingEnabled = trackDrawingEnabled; + public void setShowEndMarker(boolean show) { + showEndMarker = show; } /** - * Sets whether to draw the end maker or not. + * Updates the track, start and end markers, and waypoints. * - * @param showEndMarker true to draw end marker + * @param googleMap the google map + * @param paths the paths + * @param reload true to reload all points */ - public void setShowEndMarker(boolean showEndMarker) { - this.showEndMarker = showEndMarker; - } - - /** - * Sets my location. - * - * @param myLocation my location - */ - public void setMyLocation(Location myLocation) { - this.myLocation = myLocation; - } - - /** - * Sets the heading. - * - * @param heading the heading - * @return true if the visible heading has changed. - */ - public boolean setHeading(float heading) { - /* - * Use -heading because the arrow images are counter-clockwise rather than - * clockwise. - */ - int index = Math.round(-heading / 360 * 18); - while (index < 0) { - index += 18; - } - while (index > 17) { - index -= 18; - } - if (index != headingIndex) { - headingIndex = index; - return true; - } else { - return false; - } - } - - @Override - public void draw(Canvas canvas, MapView mapView, boolean shadow) { - if (shadow) { - return; - } - - // It's safe to keep projection within a single draw operation - Projection projection = getMapProjection(mapView); - if (projection == null) { - Log.w(TAG, "No projection, unable to draw."); - return; - } - - if (trackDrawingEnabled) { - // Get the current viewing Rect - Rect viewRect = getMapViewRect(mapView); - - // Draw the selected track - drawTrack(canvas, projection, viewRect); - - // Draw the start and end markers - drawMarkers(canvas, projection); - - // Draw the waypoints - drawWaypoints(canvas, projection); - } - - // Draw the current location - drawMyLocation(canvas, projection); - } - - @Override - public boolean onTap(GeoPoint geoPoint, MapView mapView) { - if (geoPoint.equals(mapView.getMapCenter())) { - /* - * There is (unfortunately) no good way to determine whether the tap was - * caused by an actual tap on the screen or the track ball. If the - * location is equal to the map center,then it was a track ball press with - * very high likelihood. - */ - return false; - } - final Location tapLocation = LocationUtils.getLocation(geoPoint); - double minDistance = Double.MAX_VALUE; - Waypoint waypoint = null; - synchronized (waypoints) { - for (int i = 0; i < waypoints.size(); i++) { - final Location waypointLocation = waypoints.get(i).getLocation(); - if (waypointLocation == null) { - continue; - } - final double distance = waypointLocation.distanceTo(tapLocation); - if (distance < minDistance) { - minDistance = distance; - waypoint = waypoints.get(i); - } - } - } - - if (waypoint != null && minDistance < 15000000 / Math.pow(2, mapView.getZoomLevel())) { - Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class) - .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, waypoint.getId()); - context.startActivity(intent); - return true; - } - return super.onTap(geoPoint, mapView); - } - - /** - * Gets the points. - */ - @VisibleForTesting - public List getPoints() { - return points; - } - - /** - * Gets the track path painter. - */ - @VisibleForTesting - public TrackPathPainter getTrackPathPainter() { - return trackPathPainter; - } - - /** - * Sets the track path painter. - * - * @param trackPathPainter the track path painter - */ - @VisibleForTesting - public void setTrackPathPainter(TrackPathPainter trackPathPainter) { - this.trackPathPainter = trackPathPainter; - } - - /** - * Gets the map view projection. - * - * @param mapView the map view - */ - @VisibleForTesting - protected Projection getMapProjection(MapView mapView) { - return mapView.getProjection(); - } - - /** - * Gets the map view Rect. - * - * @param mapView the map view - */ - @VisibleForTesting - protected Rect getMapViewRect(MapView mapView) { - int width = mapView.getLongitudeSpan(); - int height = mapView.getLatitudeSpan(); - int centerX = mapView.getMapCenter().getLongitudeE6(); - int centerY = mapView.getMapCenter().getLatitudeE6(); - return new Rect(centerX - width / 2, centerY - height / 2, centerX + width / 2, centerY + height - / 2); - } - - /** - * Gets number of locations. - */ - @VisibleForTesting - int getNumLocations() { - synchronized (points) { - return points.size() + pendingPoints.size(); - } - } - - /** - * Gets number of waypoints. - */ - @VisibleForTesting - int getNumWaypoints() { - synchronized (waypoints) { - return waypoints.size(); - } - } - - /** - * Draws the track. - * - * @param canvas the canvas - * @param projection the projection - * @param viewRect the view rect - */ - private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) { - boolean draw; - - synchronized (points) { - // Merge the pending points with the list of cached locations. - GeoPoint referencePoint = projection.fromPixels(0, 0); - int newPoints = pendingPoints.drainTo(points); - boolean newProjection = !viewRect.equals(lastViewRect) - || !referencePoint.equals(lastReferencePoint); - // Call updateState first to trigger its side effects. - boolean currentPathValid = !trackPathPainter.updateState() && !newProjection - && trackPathPainter.hasPath(); - if (newPoints == 0 && currentPathValid) { - // No need to update - draw = true; + public void update(GoogleMap googleMap, ArrayList paths, boolean reload) { + synchronized (locations) { + // Merge pendingLocations with locations + int newLocations = pendingLocations.drainTo(locations); + boolean needReload = reload || trackPath.updateState(); + if (needReload) { + googleMap.clear(); + paths.clear(); + trackPath.updatePath(googleMap, paths, 0, locations); + updateStartAndEndMarkers(googleMap); + updateWaypoints(googleMap); } else { - int numPoints = points.size(); - if (numPoints < 2) { - // Not enough points to draw a path - draw = false; - } else if (currentPathValid) { - // Incremental update of the path - draw = true; - trackPathPainter.updatePath(projection, viewRect, numPoints - newPoints, points); - } else { - // Reload the path - draw = true; - trackPathPainter.clearPath(); - trackPathPainter.updatePath(projection, viewRect, 0, points); + if (newLocations != 0) { + int numLocations = locations.size(); + trackPath.updatePath(googleMap, paths, numLocations - newLocations, locations); } } - lastReferencePoint = referencePoint; - lastViewRect = viewRect; - } - if (draw) { - trackPathPainter.drawPath(canvas); } } /** - * Draws the start and end markers. + * Updates the start and end markers. * - * @param canvas the canvas - * @param projection the projection + * @param googleMap the google map */ - private void drawMarkers(Canvas canvas, Projection projection) { - int offsetY = (int) (markerHeight * Constants.MARKER_Y_OFFSET_PERCENTAGE); - // Draw the end marker + private void updateStartAndEndMarkers(GoogleMap googleMap) { + // Add the end marker if (showEndMarker) { - for (int i = points.size() - 1; i >= 0; i--) { - if (points.get(i).valid) { - drawElement( - canvas, projection, points.get(i).geoPoint, endMarker, -markerWidth / 2, -offsetY); + for (int i = locations.size() - 1; i >= 0; i--) { + CachedLocation cachedLocation = locations.get(i); + if (cachedLocation.valid) { + Marker.Options options = Model.newMarkerOptions().position(cachedLocation.getLatLng()) + .anchor(MARKER_X_ANCHOR, MARKER_Y_ANCHOR).draggable(false).visible(true) + .icon(BitmapDescriptors.fromResource(R.drawable.red_dot)); + googleMap.addMarker(options); break; } } } - // Draw the start marker - for (int i = 0; i < points.size(); i++) { - if (points.get(i).valid) { - drawElement( - canvas, projection, points.get(i).geoPoint, startMarker, -markerWidth / 2, -offsetY); + // Add the start marker + for (int i = 0; i < locations.size(); i++) { + CachedLocation cachedLocation = locations.get(i); + if (cachedLocation.valid) { + Marker.Options options = Model.newMarkerOptions().position(cachedLocation.getLatLng()) + .anchor(MARKER_X_ANCHOR, MARKER_Y_ANCHOR).draggable(false).visible(true) + .icon(BitmapDescriptors.fromResource(R.drawable.green_dot)); + googleMap.addMarker(options); break; } } } /** - * Draws the waypoints. + * Updates the waypoints. * - * @param canvas the canvas - * @param projection the projection + * @param googleMap the google map. */ - private void drawWaypoints(Canvas canvas, Projection projection) { + private void updateWaypoints(GoogleMap googleMap) { synchronized (waypoints) { - int offsetX = (int) (markerWidth * Constants.WAYPOINT_X_OFFSET_PERCENTAGE); for (Waypoint waypoint : waypoints) { Location location = waypoint.getLocation(); - Drawable drawable = waypoint.getType() == Waypoint.TYPE_STATISTICS ? statsMarker - : waypointMarker; - drawElement(canvas, projection, LocationUtils.getGeoPoint(location), drawable, -offsetX, - -markerHeight); + LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); + int drawableId = waypoint.getType() == Waypoint.TYPE_STATISTICS ? R.drawable.yellow_pushpin + : R.drawable.blue_pushpin; + Marker.Options options = Model.newMarkerOptions().position(latLng) + .anchor(WAYPOINT_X_ANCHOR, WAYPOINT_Y_ANCHOR).draggable(false).visible(true) + .icon(BitmapDescriptors.fromResource(drawableId)) + .title(String.valueOf(waypoint.getId())); + googleMap.addMarker(options); } } } - - /** - * Draws my location. - * - * @param canvas the canvas - * @param projection the projection - */ - private void drawMyLocation(Canvas canvas, Projection projection) { - if (myLocation == null) { - return; - } - Point point = drawElement(canvas, projection, LocationUtils.getGeoPoint(myLocation), - arrows[headingIndex], -(arrowWidth / 2), -(arrowHeight / 2)); - // Draw the error circle - float radius = projection.metersToEquatorPixels(myLocation.getAccuracy()); - canvas.drawCircle(point.x, point.y, radius, errorCirclePaint); - } - - /** - * Draws an element. - * - * @param canvas the canvas - * @param projection the projection - * @param geoPoint the geo point - * @param drawable the drawable - * @param offsetX the x offset - * @param offsetY the y offset - * @return the point of the drawing. - */ - private Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint, - Drawable drawable, int offsetX, int offsetY) { - Point point = new Point(); - projection.toPixels(geoPoint, point); - canvas.save(); - canvas.translate(point.x + offsetX, point.y + offsetY); - drawable.draw(canvas); - canvas.restore(); - return point; - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java index f44e00710..94f778baf 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java @@ -28,7 +28,7 @@ import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment; import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment.DeleteOneTrackCaller; import com.google.android.apps.mytracks.fragments.FrequencyDialogFragment; import com.google.android.apps.mytracks.fragments.InstallEarthDialogFragment; -import com.google.android.apps.mytracks.fragments.MapFragment; +import com.google.android.apps.mytracks.fragments.MyTracksMapFragment; import com.google.android.apps.mytracks.fragments.StatsFragment; import com.google.android.apps.mytracks.io.file.SaveActivity; import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; @@ -36,7 +36,6 @@ import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest; import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; import com.google.android.apps.mytracks.settings.SettingsActivity; import com.google.android.apps.mytracks.util.AnalyticsUtils; -import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.IntentUtils; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils; @@ -63,7 +62,7 @@ import java.util.List; /** * An activity to show the track detail. - * + * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ @@ -74,19 +73,18 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del private static final String TAG = TrackDetailActivity.class.getSimpleName(); private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key"; - + // The following are set in onCreate private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackDataHub trackDataHub; - private View mapViewContainer; private TabHost tabHost; private TabManager tabManager; private TrackController trackController; - + // From intent private long trackId; private long markerId; - + // Preferences private long recordingTrackId; private boolean recordingTrackPaused; @@ -136,7 +134,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del if (key != null) { boolean isRecording = trackId == recordingTrackId; updateMenuItems(isRecording, recordingTrackPaused); - trackController.update(isRecording, recordingTrackPaused); + trackController.update(isRecording, recordingTrackPaused); } } }; @@ -170,25 +168,6 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del } }; - /** - * We are not displaying driving directions. Just an arbitrary track that is - * not associated to any licensed mapping data. Therefore it should be okay to - * return false here and still comply with the terms of service. - */ - @Override - protected boolean isRouteDisplayed() { - return false; - } - - /** - * We are displaying a location. This needs to return true in order to comply - * with the terms of service. - */ - @Override - protected boolean isLocationDisplayed() { - return true; - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -199,18 +178,16 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null); - trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); + trackRecordingServiceConnection = new TrackRecordingServiceConnection( + this, bindChangedCallback); trackDataHub = TrackDataHub.newInstance(this); - mapViewContainer = getLayoutInflater().inflate(R.layout.map, null); - ApiAdapterFactory.getApiAdapter().disableHardwareAccelerated(mapViewContainer); - tabHost = (TabHost) findViewById(android.R.id.tabhost); tabHost.setup(); tabManager = new TabManager(this, tabHost, R.id.realtabcontent); - TabSpec mapTabSpec = tabHost.newTabSpec(MapFragment.MAP_FRAGMENT_TAG).setIndicator( + TabSpec mapTabSpec = tabHost.newTabSpec(MyTracksMapFragment.MAP_FRAGMENT_TAG).setIndicator( getString(R.string.track_detail_map_tab), getResources().getDrawable(R.drawable.tab_map)); - tabManager.addTab(mapTabSpec, MapFragment.class, null); + tabManager.addTab(mapTabSpec, MyTracksMapFragment.class, null); TabSpec chartTabSpec = tabHost.newTabSpec(ChartFragment.CHART_FRAGMENT_TAG).setIndicator( getString(R.string.track_detail_chart_tab), getResources().getDrawable(R.drawable.tab_chart)); @@ -222,7 +199,8 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del if (savedInstanceState != null) { tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAB_TAG_KEY)); } - trackController = new TrackController(this, trackRecordingServiceConnection, false, recordListener, stopListener); + trackController = new TrackController( + this, trackRecordingServiceConnection, false, recordListener, stopListener); showMarker(); } @@ -274,7 +252,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del super.onDestroy(); trackRecordingServiceConnection.unbind(); } - + @Override protected void onHomeSelected() { Intent intent = IntentUtils.newIntent(this, TrackListActivity.class); @@ -427,13 +405,6 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del return trackRecordingServiceConnection; } - /** - * Gets the map view container. - */ - public View getMapViewContainer() { - return mapViewContainer; - } - /** * Gets the {@link TrackDataHub}. */ @@ -475,10 +446,10 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del */ private void showMarker() { if (markerId != -1L) { - MapFragment mapFragmet = (MapFragment) getSupportFragmentManager() - .findFragmentByTag(MapFragment.MAP_FRAGMENT_TAG); + MyTracksMapFragment mapFragmet = (MyTracksMapFragment) getSupportFragmentManager() + .findFragmentByTag(MyTracksMapFragment.MAP_FRAGMENT_TAG); if (mapFragmet != null) { - tabHost.setCurrentTabByTag(MapFragment.MAP_FRAGMENT_TAG); + tabHost.setCurrentTabByTag(MyTracksMapFragment.MAP_FRAGMENT_TAG); mapFragmet.showMarker(trackId, markerId); } else { Log.e(TAG, "MapFragment is null"); @@ -488,7 +459,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del /** * Updates the menu items. - * + * * @param isRecording true if recording */ private void updateMenuItems(boolean isRecording, boolean isPaused) { @@ -516,8 +487,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del String title; if (isRecording) { - title = getString( - isPaused ? R.string.generic_paused : R.string.generic_recording); + title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording); } else { Track track = MyTracksProviderUtils.Factory.get(this).getTrack(trackId); title = track != null ? track.getName() : getString(R.string.my_tracks_app_name); @@ -527,7 +497,7 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del /** * Starts the {@link SaveActivity} to save a track. - * + * * @param trackFileFormat the track file format */ private void startSaveActivity(TrackFileFormat trackFileFormat) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java deleted file mode 100644 index ba236bccf..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Copyright 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.google.android.apps.mytracks.fragments; - -import com.google.android.apps.mytracks.MapOverlay; -import com.google.android.apps.mytracks.TrackDetailActivity; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; -import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.TrackDataHub; -import com.google.android.apps.mytracks.content.TrackDataListener; -import com.google.android.apps.mytracks.content.TrackDataType; -import com.google.android.apps.mytracks.content.Waypoint; -import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.ApiAdapterFactory; -import com.google.android.apps.mytracks.util.GeoRect; -import com.google.android.apps.mytracks.util.GoogleLocationUtils; -import com.google.android.apps.mytracks.util.LocationUtils; -import com.google.android.maps.GeoPoint; -import com.google.android.maps.MapController; -import com.google.android.maps.MapView; -import com.google.android.maps.Overlay; -import com.google.android.maps.mytracks.R; - -import android.content.Intent; -import android.location.Location; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; - -import java.util.EnumSet; -import java.util.List; - -/** - * A fragment to display map to the user. - * - * @author Leif Hendrik Wilden - * @author Rodrigo Damazio - */ -public class MapFragment extends Fragment - implements View.OnTouchListener, View.OnClickListener, TrackDataListener { - - public static final String MAP_FRAGMENT_TAG = "mapFragment"; - - private static final String CURRENT_LOCATION_KEY = "current_location_key"; - private static final String KEEP_MY_LOCATION_VISIBLE_KEY = "keep_my_location_visible_key"; - - private TrackDataHub trackDataHub; - - // True to keep my location visible. - private boolean keepMyLocationVisible; - - // True to zoom to my location. Only apply when keepMyLocationVisible is true. - private boolean zoomToMyLocation; - - // The track id of the marker to show. - private long markerTrackId; - - // The marker id to show - private long markerId; - - // The current selected track id. Set in onSelectedTrackChanged. - private long currentSelectedTrackId; - - // The current location. Set in onCurrentLocationChanged. - private Location currentLocation; - - // UI elements - private View mapViewContainer; - private MapView mapView; - private MapOverlay mapOverlay; - private ImageButton myLocationImageButton; - private TextView messageTextView; - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer(); - mapView = (MapView) mapViewContainer.findViewById(R.id.map_view); - mapOverlay = new MapOverlay(getActivity()); - - List overlays = mapView.getOverlays(); - overlays.clear(); - overlays.add(mapOverlay); - - mapView.requestFocus(); - mapView.setOnTouchListener(this); - mapView.setBuiltInZoomControls(true); - myLocationImageButton = (ImageButton) mapViewContainer.findViewById(R.id.map_my_location); - myLocationImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showMyLocation(); - } - }); - messageTextView = (TextView) mapViewContainer.findViewById(R.id.map_message); - - ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity()); - return mapViewContainer; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (savedInstanceState != null) { - keepMyLocationVisible = savedInstanceState.getBoolean(KEEP_MY_LOCATION_VISIBLE_KEY, false); - currentLocation = (Location) savedInstanceState.getParcelable(CURRENT_LOCATION_KEY); - if (currentLocation != null) { - updateCurrentLocation(); - } - } - } - - @Override - public void onResume() { - super.onResume(); - resumeTrackDataHub(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEEP_MY_LOCATION_VISIBLE_KEY, keepMyLocationVisible); - if (currentLocation != null) { - outState.putParcelable(CURRENT_LOCATION_KEY, currentLocation); - } - } - - @Override - public void onPause() { - super.onPause(); - pauseTrackDataHub(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent(); - if (parentViewGroup != null) { - parentViewGroup.removeView(mapViewContainer); - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) { - menuInflator.inflate(R.menu.map, menu); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - int titleId = R.string.menu_satellite_mode; - if (mapView != null) { - titleId = mapView.isSatellite() ? R.string.menu_map_mode : R.string.menu_satellite_mode; - } - menu.findItem(R.id.map_satellite_mode).setTitle(titleId); - super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (mapView != null && menuItem.getItemId() == R.id.map_satellite_mode) { - mapView.setSatellite(!mapView.isSatellite()); - return true; - } - return super.onOptionsItemSelected(menuItem); - } - - /** - * Shows my location. - */ - private void showMyLocation() { - updateTrackDataHub(); - keepMyLocationVisible = true; - zoomToMyLocation = true; - if (currentLocation != null) { - updateCurrentLocation(); - } - } - - /** - * Shows the marker. - * - * @param id the marker id - */ - private void showMarker(long id) { - MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity()); - Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id); - if (waypoint != null && waypoint.getLocation() != null) { - keepMyLocationVisible = false; - GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6), - (int) (waypoint.getLocation().getLongitude() * 1E6)); - mapView.getController().setCenter(center); - mapView.getController().setZoom(mapView.getMaxZoomLevel()); - mapView.invalidate(); - } - } - - /** - * Shows the marker. - * - * @param trackId the track id - * @param id the marker id - */ - public void showMarker(long trackId, long id) { - /* - * Synchronize to prevent race condition in changing markerTrackId and - * markerId variables. - */ - synchronized (this) { - if (trackId == currentSelectedTrackId) { - showMarker(id); - markerTrackId = -1L; - markerId = -1L; - return; - } - markerTrackId = trackId; - markerId = id; - } - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) { - if (!isVisible(currentLocation)) { - /* - * Only set to false when no longer visible. Thus can keep showing the - * current location with the next location update. - */ - keepMyLocationVisible = false; - } - } - return false; - } - - @Override - public void onClick(View v) { - if (v == messageTextView) { - Intent intent = GoogleLocationUtils.isAvailable(getActivity()) ? new Intent( - GoogleLocationUtils.ACTION_GOOGLE_LOCATION_SETTINGS) - : new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - } - - @Override - public void onLocationStateChanged(LocationState state) { - if (isResumed()) { - final String message; - final boolean isGpsDisabled; - if (!isSelectedTrackRecording()) { - message = null; - isGpsDisabled = false; - } else { - switch (state) { - case DISABLED: - String setting = getString( - GoogleLocationUtils.isAvailable(getActivity()) ? R.string.gps_google_location_settings - : R.string.gps_location_access); - message = getString(R.string.gps_disabled, setting); - isGpsDisabled = true; - currentLocation = null; - updateCurrentLocation(); - break; - case NO_FIX: - message = getString(R.string.gps_wait_for_signal); - isGpsDisabled = false; - break; - case BAD_FIX: - message = getString(R.string.gps_wait_for_better_signal); - isGpsDisabled = false; - break; - case GOOD_FIX: - message = null; - isGpsDisabled = false; - break; - default: - throw new IllegalArgumentException("Unexpected state: " + state); - } - } - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - if (message == null) { - messageTextView.setVisibility(View.GONE); - return; - } - messageTextView.setText(message); - messageTextView.setVisibility(View.VISIBLE); - if (isGpsDisabled) { - Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show(); - - // Click to show the location source settings - messageTextView.setOnClickListener(MapFragment.this); - } else { - messageTextView.setOnClickListener(null); - } - } - } - }); - } - } - - @Override - public void onLocationChanged(Location location) { - if (isResumed()) { - currentLocation = location; - updateCurrentLocation(); - } - } - - @Override - public void onHeadingChanged(double heading) { - if (isResumed()) { - if (mapOverlay.setHeading((float) heading)) { - mapView.postInvalidate(); - } - } - } - - @Override - public void onSelectedTrackChanged(final Track track) { - if (isResumed()) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (isResumed()) { - boolean hasTrack = track != null; - mapOverlay.setTrackDrawingEnabled(hasTrack); - - if (hasTrack) { - synchronized (this) { - /* - * Synchronize to prevent race condition in changing - * markerTrackId and markerId variables. - */ - currentSelectedTrackId = track.getId(); - updateMap(track); - } - mapOverlay.setShowEndMarker(!isSelectedTrackRecording()); - } - mapView.invalidate(); - } - } - }); - } - } - - @Override - public void onTrackUpdated(Track track) { - // We don't care. - } - - @Override - public void clearTrackPoints() { - if (isResumed()) { - mapOverlay.clearPoints(); - } - } - - @Override - public void onSampledInTrackPoint(Location location) { - if (isResumed()) { - mapOverlay.addLocation(location); - } - } - - @Override - public void onSampledOutTrackPoint(Location location) { - // We don't care. - } - - @Override - public void onSegmentSplit(Location location) { - if (isResumed()) { - mapOverlay.addSegmentSplit(); - } - } - - @Override - public void onNewTrackPointsDone() { - if (isResumed()) { - mapView.postInvalidate(); - } - } - - @Override - public void clearWaypoints() { - if (isResumed()) { - mapOverlay.clearWaypoints(); - } - } - - @Override - public void onNewWaypoint(Waypoint waypoint) { - if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { - // TODO: Optimize locking inside addWaypoint - mapOverlay.addWaypoint(waypoint); - } - } - - @Override - public void onNewWaypointsDone() { - if (isResumed()) { - mapView.postInvalidate(); - } - } - - @Override - public boolean onMetricUnitsChanged(boolean metric) { - // We don't care. - return false; - } - - @Override - public boolean onReportSpeedChanged(boolean reportSpeed) { - // We don't care. - return false; - } - - @Override - public boolean onMinRecordingDistanceChanged(int minRecordingDistance) { - // We don't care. - return false; - } - - /** - * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void resumeTrackDataHub() { - trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); - trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK, - TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, - TrackDataType.LOCATION, TrackDataType.HEADING)); - } - - /** - * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void pauseTrackDataHub() { - trackDataHub.unregisterTrackDataListener(this); - trackDataHub = null; - } - - /** - * Updates the trackDataHub. Needs to be synchronized because trackDataHub can - * be accessed by multiple threads. - */ - private synchronized void updateTrackDataHub() { - if (trackDataHub != null) { - trackDataHub.forceUpdateLocation(); - } - } - - /** - * Returns true if the selected track is recording. Needs to be synchronized - * because trackDataHub can be accessed by multiple threads. - */ - private synchronized boolean isSelectedTrackRecording() { - return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); - } - - /** - * Updates the map by either zooming to the requested marker or showing the - * track. - * - * @param track the track - */ - private void updateMap(Track track) { - if (track.getId() == markerTrackId) { - // Show the marker - showMarker(markerId); - - markerTrackId = -1L; - markerId = -1L; - } else { - // Show the track - showTrack(track); - } - } - - /** - * Returns true if the location is visible. - * - * @param location the location - */ - private boolean isVisible(Location location) { - if (location == null || mapView == null) { - return false; - } - GeoPoint mapCenter = mapView.getMapCenter(); - int latitudeSpan = mapView.getLatitudeSpan(); - int longitudeSpan = mapView.getLongitudeSpan(); - - /* - * The bottom of the mapView is obscured by the zoom controls, subtract its - * height from the visible area. - */ - GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight()); - GeoPoint zoomControlTop = mapView.getProjection().fromPixels( - 0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight()); - int zoomControlMargin = Math.abs( - zoomControlTop.getLatitudeE6() - zoomControlBottom.getLatitudeE6()); - GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan); - geoRect.top += zoomControlMargin; - - GeoPoint geoPoint = LocationUtils.getGeoPoint(location); - return geoRect.contains(geoPoint); - } - - /** - * Updates the current location and centers it if necessary. - */ - private void updateCurrentLocation() { - if (mapOverlay == null || mapView == null) { - return; - } - - mapOverlay.setMyLocation(currentLocation); - mapView.postInvalidate(); - - if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) { - GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation); - MapController mapController = mapView.getController(); - mapController.animateTo(geoPoint); - if (zoomToMyLocation) { - // Only zoom in the first time we show the location. - zoomToMyLocation = false; - if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) { - mapController.setZoom(mapView.getMaxZoomLevel()); - } - } - } - } - - /** - * Shows the track. - * - * @param track the track - */ - private void showTrack(Track track) { - if (mapView == null || track == null || track.getNumberOfPoints() < 2) { - return; - } - - TripStatistics tripStatistics = track.getTripStatistics(); - int bottom = tripStatistics.getBottom(); - int left = tripStatistics.getLeft(); - int latitudeSpanE6 = tripStatistics.getTop() - bottom; - int longitudeSpanE6 = tripStatistics.getRight() - left; - if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0 - && longitudeSpanE6 < 360E6) { - keepMyLocationVisible = false; - GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2); - if (LocationUtils.isValidGeoPoint(center)) { - mapView.getController().setCenter(center); - mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6); - } - } - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/MyTracksMapFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/MyTracksMapFragment.java new file mode 100644 index 000000000..e682de56f --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/MyTracksMapFragment.java @@ -0,0 +1,667 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.android.apps.mytracks.fragments; + +import com.google.android.apps.mytracks.MapOverlay; +import com.google.android.apps.mytracks.MarkerDetailActivity; +import com.google.android.apps.mytracks.TrackDetailActivity; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.TrackDataHub; +import com.google.android.apps.mytracks.content.TrackDataListener; +import com.google.android.apps.mytracks.content.TrackDataType; +import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.stats.TripStatistics; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; +import com.google.android.apps.mytracks.util.GoogleLocationUtils; +import com.google.android.apps.mytracks.util.IntentUtils; +import com.google.android.apps.mytracks.util.LocationUtils; +import com.google.android.gms.maps.CameraUpdate; +import com.google.android.gms.maps.CameraUpdates; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.LocationSource; +import com.google.android.gms.maps.LocationSource.OnLocationChangedListener; +import com.google.android.gms.maps.OnMapInitializeListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.CameraPosition; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.Model; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.maps.mytracks.R; + +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.EnumSet; + +/** + * A fragment to display map to the user. + * + * @author Leif Hendrik Wilden + * @author Rodrigo Damazio + */ +public class MyTracksMapFragment extends SupportMapFragment + implements OnMapInitializeListener, TrackDataListener { + + public static final String MAP_FRAGMENT_TAG = "mapFragment"; + + private static final String CURRENT_LOCATION_KEY = "current_location_key"; + private static final String + KEEP_CURRENT_LOCATION_VISIBLE_KEY = "keep_current_location_visible_key"; + private static final String ZOOM_TO_CURRENT_LOCATION_KEY = "zoom_to_current_location_key"; + + private static final float DEFAULT_ZOOM_LEVEL = 18f; + + // If current zoom level is above this, no need to zoom to DEFAULT_ZOOM_LEVEL + private static final float MIN_ALLOWED_ZOOM_LEVEL = 10f; + + // Google's latitude and longitude + private static final double DEFAULT_LATITUDE = 37.423; + private static final double DEFAULT_LONGITUDE = -122.084; + + private TrackDataHub trackDataHub; + + // Current location + private Location currentLocation; + + // True to keep the currentLocation visible + private boolean keepCurrentLocationVisible; + + // True to zoom to currentLocation when it is available + private boolean zoomToCurrentLocation; + + private OnLocationChangedListener onLocationChangedListener; + + // For showing a marker + private long markerTrackId = -1L; + private long markerId = -1L; + + // Current track + private Track currentTrack; + + // Current paths + private ArrayList paths = new ArrayList(); + boolean reloadPaths = true; + + // UI elements + private GoogleMap googleMap; + private MapOverlay mapOverlay; + private ImageButton myLocationImageButton; + private TextView messageTextView; + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + setHasOptionsMenu(true); + ApiAdapterFactory.getApiAdapter().invalidMenu(getActivity()); + mapOverlay = new MapOverlay(getActivity()); + setOnMapInitializeListener(this); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + View layout = inflater.inflate(R.layout.map, container, false); + RelativeLayout mapContainer = (RelativeLayout) layout.findViewById(R.id.map_container); + mapContainer.addView(view, 0); + + myLocationImageButton = (ImageButton) layout.findViewById(R.id.map_my_location); + myLocationImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + forceUpdateLocation(); + keepCurrentLocationVisible = true; + zoomToCurrentLocation = true; + updateCurrentLocation(); + } + }); + messageTextView = (TextView) layout.findViewById(R.id.map_message); + return layout; + } + + @Override + public void onMapInitialize(GoogleMap map) { + googleMap = map; + googleMap.setMyLocationEnabled(true); + googleMap.getUiSettings().setMyLocationButtonEnabled(false); + googleMap.setIndoorEnabled(true); + googleMap.setOnMarkerClickListener(new OnMarkerClickListener() { + + @Override + public boolean onMarkerClick(Marker marker) { + if (isResumed()) { + String title = marker.getTitle(); + if (title != null && title.length() > 0) { + long id = Long.valueOf(title); + Context context = getActivity(); + Intent intent = IntentUtils.newIntent(context, MarkerDetailActivity.class) + .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id); + context.startActivity(intent); + } + } + return true; + } + }); + googleMap.setLocationSource(new LocationSource() { + + @Override + public void activate(OnLocationChangedListener listener) { + onLocationChangedListener = listener; + } + + @Override + public void deactivate() { + onLocationChangedListener = null; + } + }); + googleMap.setOnCameraChangeListener(new OnCameraChangeListener() { + + @Override + public void onCameraChange(CameraPosition cameraPosition) { + if (keepCurrentLocationVisible && currentLocation != null + && !isLocationVisible(currentLocation)) { + keepCurrentLocationVisible = false; + zoomToCurrentLocation = false; + } + } + }); + LatLng latLng = getDefaultLatLng(); + if (latLng != null) { + googleMap.moveCamera(CameraUpdates.newLatLngZoom(latLng, googleMap.getMinZoomLevel())); + } + if (isResumed()) { + resetTrackDataHub(); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState != null) { + keepCurrentLocationVisible = savedInstanceState.getBoolean( + KEEP_CURRENT_LOCATION_VISIBLE_KEY, false); + zoomToCurrentLocation = savedInstanceState.getBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, false); + currentLocation = (Location) savedInstanceState.getParcelable(CURRENT_LOCATION_KEY); + updateCurrentLocation(); + } + } + + @Override + public void onResume() { + super.onResume(); + resumeTrackDataHub(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (currentLocation != null) { + outState.putParcelable(CURRENT_LOCATION_KEY, currentLocation); + } + outState.putBoolean(KEEP_CURRENT_LOCATION_VISIBLE_KEY, keepCurrentLocationVisible); + outState.putBoolean(ZOOM_TO_CURRENT_LOCATION_KEY, zoomToCurrentLocation); + } + + @Override + public void onPause() { + super.onPause(); + pauseTrackDataHub(); + } + + /** + * Shows the marker on the map. + * + * @param trackId the track id + * @param id the marker id + */ + public void showMarker(long trackId, long id) { + /* + * Synchronize to prevent race condition in changing markerTrackId and + * markerId variables. + */ + synchronized (this) { + if (currentTrack != null && currentTrack.getId() == trackId) { + showMarker(id); + markerTrackId = -1L; + markerId = -1L; + return; + } + markerTrackId = trackId; + markerId = id; + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflator) { + menuInflator.inflate(R.menu.map, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + int type = MAP_TYPE_NORMAL; + switch (menuItem.getItemId()) { + case R.id.menu_map: + type = MAP_TYPE_NORMAL; + break; + case R.id.menu_satellite: + type = MAP_TYPE_SATELLITE; + break; + case R.id.menu_terrain: + type = MAP_TYPE_TERRAIN; + break; + default: + return super.onOptionsItemSelected(menuItem); + } + if (googleMap != null) { + googleMap.setMapType(type); + } + return true; + } + + @Override + public void onLocationStateChanged(final LocationState state) { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (!isResumed() || googleMap == null) { + return; + } + boolean myLocationEnabled = true; + if (state == LocationState.DISABLED) { + currentLocation = null; + myLocationEnabled = false; + } + googleMap.setMyLocationEnabled(myLocationEnabled); + + String message; + boolean isGpsDisabled; + if (!isSelectedTrackRecording()) { + message = null; + isGpsDisabled = false; + } else { + switch (state) { + case DISABLED: + String setting = getString(GoogleLocationUtils.isAvailable(getActivity()) + ? R.string.gps_google_location_settings + : R.string.gps_location_access); + message = getString(R.string.gps_disabled, setting); + isGpsDisabled = true; + break; + case NO_FIX: + message = getString(R.string.gps_wait_for_signal); + isGpsDisabled = false; + break; + case BAD_FIX: + message = getString(R.string.gps_wait_for_better_signal); + isGpsDisabled = false; + break; + case GOOD_FIX: + message = null; + isGpsDisabled = false; + break; + default: + throw new IllegalArgumentException("Unexpected state: " + state); + } + } + if (message == null) { + messageTextView.setVisibility(View.GONE); + return; + } + messageTextView.setText(message); + messageTextView.setVisibility(View.VISIBLE); + if (isGpsDisabled) { + Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show(); + + // Click to show the location source settings + messageTextView.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = GoogleLocationUtils.isAvailable(getActivity()) ? new Intent( + GoogleLocationUtils.ACTION_GOOGLE_LOCATION_SETTINGS) + : new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } else { + messageTextView.setOnClickListener(null); + } + } + }); + } + } + + @Override + public void onLocationChanged(final Location location) { + if (isResumed()) { + if (isSelectedTrackRecording() && currentLocation == null && location != null) { + zoomToCurrentLocation = true; + } + currentLocation = location; + updateCurrentLocation(); + } + } + + @Override + public void onHeadingChanged(double heading) { + // We don't care. + } + + @Override + public void onSelectedTrackChanged(final Track track) { + if (isResumed()) { + currentTrack = track; + boolean hasTrack = track != null; + if (hasTrack) { + mapOverlay.setShowEndMarker(!isSelectedTrackRecording()); + synchronized (this) { + /* + * Synchronize to prevent race condition in changing markerTrackId and + * markerId variables. + */ + if (track.getId() == markerTrackId) { + // Show the marker + showMarker(markerId); + + markerTrackId = -1L; + markerId = -1L; + } else { + // Show the track + showTrack(); + } + } + } + } + } + + @Override + public void onTrackUpdated(Track track) { + // We don't care. + } + + @Override + public void clearTrackPoints() { + if (isResumed()) { + mapOverlay.clearPoints(); + reloadPaths = true; + } + } + + @Override + public void onSampledInTrackPoint(final Location location) { + if (isResumed()) { + mapOverlay.addLocation(location); + } + } + + @Override + public void onSampledOutTrackPoint(Location location) { + // We don't care. + } + + @Override + public void onSegmentSplit(Location location) { + if (isResumed()) { + mapOverlay.addSegmentSplit(); + } + } + + @Override + public void onNewTrackPointsDone() { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + if (isResumed() && googleMap != null) { + mapOverlay.update(googleMap, paths, reloadPaths); + reloadPaths = false; + } + } + }); + } + } + + @Override + public void clearWaypoints() { + if (isResumed()) { + mapOverlay.clearWaypoints(); + } + } + + @Override + public void onNewWaypoint(Waypoint waypoint) { + if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) { + mapOverlay.addWaypoint(waypoint); + } + } + + @Override + public void onNewWaypointsDone() { + if (isResumed()) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + if (isResumed() && googleMap != null) { + mapOverlay.update(googleMap, paths, true); + } + } + }); + } + } + + @Override + public boolean onMetricUnitsChanged(boolean metric) { + // We don't care. + return false; + } + + @Override + public boolean onReportSpeedChanged(boolean reportSpeed) { + // We don't care. + return false; + } + + @Override + public boolean onMinRecordingDistanceChanged(int minRecordingDistance) { + // We don't care. + return false; + } + + /** + * Resumes the trackDataHub. Needs to be synchronized because the trackDataHub + * can be accessed by multiple threads. + */ + private synchronized void resumeTrackDataHub() { + trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub(); + trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.SELECTED_TRACK, + TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, + TrackDataType.LOCATION, TrackDataType.HEADING)); + } + + /** + * Pauses the trackDataHub. Needs to be synchronized because the trackDataHub + * can be accessed by multiple threads. + */ + private synchronized void pauseTrackDataHub() { + if (trackDataHub != null) { + trackDataHub.unregisterTrackDataListener(this); + } + trackDataHub = null; + } + + /** + * Resets the trackDataHub. Needs to be synchronized because the trackDataHub + * can be accessed by multiple threads. + */ + private synchronized void resetTrackDataHub() { + pauseTrackDataHub(); + resumeTrackDataHub(); + } + + /** + * Returns true if the selected track is recording. Needs to be synchronized + * because the trackDataHub can be accessed by multiple threads. + */ + private synchronized boolean isSelectedTrackRecording() { + return trackDataHub != null && trackDataHub.isSelectedTrackRecording(); + } + + /** + * Forces update location. Needs to be synchronized because the trackDataHub + * can be accessed by multiple threads. + */ + private synchronized void forceUpdateLocation() { + if (trackDataHub != null) { + trackDataHub.forceUpdateLocation(); + } + } + + /** + * Updates the current location and zoom to it if necessary. + */ + private void updateCurrentLocation() { + getActivity().runOnUiThread(new Runnable() { + public void run() { + if (!isResumed() || googleMap == null || onLocationChangedListener == null + || currentLocation == null) { + return; + } + onLocationChangedListener.onLocationChanged(currentLocation); + if (zoomToCurrentLocation + || (keepCurrentLocationVisible && !isLocationVisible(currentLocation))) { + LatLng latLng = new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude()); + googleMap.animateCamera(CameraUpdates.newLatLngZoom(latLng, getDefaultZoomLevel())); + zoomToCurrentLocation = false; + } + }; + }); + } + + /** + * Sets the camera over a track. + */ + private void showTrack() { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (!isResumed() || googleMap == null || currentTrack == null + || currentTrack.getNumberOfPoints() < 2) { + return; + } + + TripStatistics tripStatistics = currentTrack.getTripStatistics(); + int latitudeSpanE6 = tripStatistics.getTop() - tripStatistics.getBottom(); + int longitudeSpanE6 = tripStatistics.getRight() - tripStatistics.getLeft(); + if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0 + && longitudeSpanE6 < 360E6) { + LatLng southWest = new LatLng( + tripStatistics.getBottomDegrees(), tripStatistics.getLeftDegrees()); + LatLng northEast = new LatLng( + tripStatistics.getTopDegrees(), tripStatistics.getRightDegrees()); + LatLngBounds bounds = Model.newLatLngBounds(southWest, northEast); + CameraUpdate cameraUpdate = CameraUpdates.newLatLngBounds(bounds, 32); + googleMap.moveCamera(cameraUpdate); + } + } + }); + } + + /** + * Sets the camera over a marker. + * + * @param id the marker id + */ + private void showMarker(final long id) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if (!isResumed() || googleMap == null) { + return; + } + MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity()); + Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id); + if (waypoint != null && waypoint.getLocation() != null) { + Location location = waypoint.getLocation(); + LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); + keepCurrentLocationVisible = false; + zoomToCurrentLocation = false; + CameraUpdate cameraUpdate = CameraUpdates.newLatLngZoom(latLng, getDefaultZoomLevel()); + googleMap.moveCamera(cameraUpdate); + } + } + }); + } + + /** + * Gets the default LatLng. + */ + private LatLng getDefaultLatLng() { + MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(getActivity()); + Location location = myTracksProviderUtils.getLastValidTrackPoint(); + if (location != null) { + return new LatLng(location.getLatitude(), location.getLongitude()); + } else { + return new LatLng(DEFAULT_LATITUDE, DEFAULT_LONGITUDE); + } + } + + /** + * Gets the default zoom level. Needs to run on the UI thread. + */ + private float getDefaultZoomLevel() { + if (googleMap == null) { + return DEFAULT_ZOOM_LEVEL; + } + float zoom = googleMap.getCameraPosition().getZoom(); + if (zoom <= MIN_ALLOWED_ZOOM_LEVEL) { + return DEFAULT_ZOOM_LEVEL; + } + return zoom; + } + + /** + * Returns true if the location is visible. Needs to run on the UI thread. + * + * @param location the location + */ + private boolean isLocationVisible(Location location) { + if (location == null || googleMap == null) { + return false; + } + LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); + return googleMap.getProjection().getVisibleRegion().getLatLngBounds().contains(latLng); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java deleted file mode 100644 index 9609d1fe6..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.maps; - -import com.google.android.apps.mytracks.ColoredPath; -import com.google.android.apps.mytracks.MapOverlay.CachedLocation; -import com.google.android.maps.GeoPoint; -import com.google.android.maps.Projection; -import com.google.android.maps.mytracks.R; -import com.google.common.annotations.VisibleForTesting; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; - -import java.util.ArrayList; -import java.util.List; - -/** - * A path painter that varies the path colors based on fixed speeds or average - * speed margin depending of the TrackPathDescriptor passed to its constructor. - * - * @author Vangelis S. - */ -public class DynamicSpeedTrackPathPainter implements TrackPathPainter { - private final TrackPathDescriptor trackPathDescriptor; - private final Paint slowPaint; - private final Paint mediumPaint; - private final Paint fastPaint; - private final List coloredPaths; - - public DynamicSpeedTrackPathPainter(Context context, TrackPathDescriptor trackPathDescriptor) { - this.trackPathDescriptor = trackPathDescriptor; - slowPaint = TrackPathUtils.getPaint(context, R.color.slow_path); - mediumPaint = TrackPathUtils.getPaint(context, R.color.normal_path); - fastPaint = TrackPathUtils.getPaint(context, R.color.fast_path); - coloredPaths = new ArrayList(); - } - - @Override - public boolean hasPath() { - return !coloredPaths.isEmpty(); - } - - @Override - public boolean updateState() { - return trackPathDescriptor.updateState(); - } - - @Override - public void updatePath( - Projection projection, Rect viewRect, int startIndex, List points) { - boolean hasLastPoint = startIndex != 0 && points.get(startIndex -1).isValid(); - Point point = new Point(); - if (hasLastPoint) { - GeoPoint geoPoint = points.get(startIndex -1).getGeoPoint(); - projection.toPixels(geoPoint, point); - } - boolean newSegment = !hasLastPoint; - // Assume if last point exists, it is visible - boolean lastPointVisible = hasLastPoint; - int slowSpeed = trackPathDescriptor.getSlowSpeed(); - int normalSpeed = trackPathDescriptor.getNormalSpeed(); - - for (int i = startIndex; i < points.size(); ++i) { - CachedLocation cachedLocation = points.get(i); - - // If not valid, start a new segment - if (!cachedLocation.isValid()) { - newSegment = true; - continue; - } - - GeoPoint geoPoint = cachedLocation.getGeoPoint(); - // Check if this breaks the existing segment. - boolean pointVisible = viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6()); - if (!pointVisible && !lastPointVisible) { - // This point and the last point are both outside visible area. - newSegment = true; - } - lastPointVisible = pointVisible; - - // Either update point or draw a line from the last point - if (newSegment) { - projection.toPixels(geoPoint, point); - newSegment = false; - } else { - ColoredPath coloredPath; - if (cachedLocation.getSpeed() <= slowSpeed) { - coloredPath = new ColoredPath(slowPaint); - } else if (cachedLocation.getSpeed() <= normalSpeed) { - coloredPath = new ColoredPath(mediumPaint); - } else { - coloredPath = new ColoredPath(fastPaint); - } - coloredPath.getPath().moveTo(point.x, point.y); - projection.toPixels(geoPoint, point); - coloredPath.getPath().lineTo(point.x, point.y); - coloredPaths.add(coloredPath); - } - } - } - - @Override - public void clearPath() { - coloredPaths.clear(); - } - - @Override - public void drawPath(Canvas canvas) { - for (int i = 0; i < coloredPaths.size(); i++) { - ColoredPath coloredPath = coloredPaths.get(i); - canvas.drawPath(coloredPath.getPath(), coloredPath.getPathPaint()); - } - } - - /** - * Gets the colored paths. - */ - @VisibleForTesting - List getColoredPaths() { - return coloredPaths; - } -} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/MultiColorTrackPath.java b/MyTracks/src/com/google/android/apps/mytracks/maps/MultiColorTrackPath.java new file mode 100644 index 000000000..a16785398 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/MultiColorTrackPath.java @@ -0,0 +1,114 @@ +/* + * 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.maps; + +import com.google.android.apps.mytracks.MapOverlay.CachedLocation; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.maps.mytracks.R; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +/** + * A path painter that varies the path colors based on fixed speeds or average + * speed margin depending of the TrackPathDescriptor passed to its constructor. + * + * @author Vangelis S. + */ +public class MultiColorTrackPath implements TrackPath { + private final TrackPathDescriptor trackPathDescriptor; + private final int slowColor; + private final int normalColor; + private final int fastColor; + + public MultiColorTrackPath(Context context, TrackPathDescriptor trackPathDescriptor) { + this.trackPathDescriptor = trackPathDescriptor; + slowColor = context.getResources().getColor(R.color.slow_path); + normalColor = context.getResources().getColor(R.color.normal_path); + fastColor = context.getResources().getColor(R.color.fast_path); + } + + @Override + public boolean updateState() { + return trackPathDescriptor.updateState(); + } + + @Override + public void updatePath(GoogleMap googleMap, ArrayList paths, int startIndex, + List locations) { + if (googleMap == null) { + return; + } + if (startIndex >= locations.size()) { + return; + } + + boolean newSegment = startIndex == 0 || !locations.get(startIndex - 1).isValid(); + LatLng lastLatLng = startIndex != 0 ? locations.get(startIndex -1).getLatLng() : null; + + ArrayList lastSegmentPoints = new ArrayList(); + int lastSegmentColor = paths.size() != 0 ? paths.get(paths.size() - 1).getColor() : slowColor; + boolean useLastPolyline = true; + + for (int i = startIndex; i < locations.size(); ++i) { + CachedLocation cachedLocation = locations.get(i); + + // If not valid, start a new segment + if (!cachedLocation.isValid()) { + newSegment = true; + lastLatLng = null; + continue; + } + LatLng latLng = cachedLocation.getLatLng(); + int color = getColor(cachedLocation.getSpeed()); + + // Either update point or draw a line from the last point + if (newSegment) { + TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline); + useLastPolyline = false; + lastSegmentColor = color; + newSegment = false; + } + if (lastSegmentColor == color) { + lastSegmentPoints.add(latLng); + } else { + TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline); + useLastPolyline = false; + if (lastLatLng != null) { + lastSegmentPoints.add(lastLatLng); + } + lastSegmentPoints.add(latLng); + lastSegmentColor = color; + } + lastLatLng = latLng; + } + TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, lastSegmentColor, useLastPolyline); + } + + private int getColor(int speed) { + if (speed <= trackPathDescriptor.getSlowSpeed()) { + return slowColor; + } else if (speed <= trackPathDescriptor.getNormalSpeed()) { + return normalColor; + } else { + return fastColor; + } + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPath.java b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPath.java new file mode 100644 index 000000000..caf03b801 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPath.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012 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.maps; + +import com.google.android.apps.mytracks.MapOverlay.CachedLocation; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Polyline; +import com.google.android.maps.mytracks.R; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +/** + * A single color track path. + * + * @author Jimmy Shih + */ +public class SingleColorTrackPath implements TrackPath { + + final int color; + + public SingleColorTrackPath(Context context) { + color = context.getResources().getColor(R.color.fast_path); + } + + @Override + public boolean updateState() { + return false; + } + + @Override + public void updatePath(GoogleMap googleMap, ArrayList paths, int startIndex, + List locations) { + if (googleMap == null) { + return; + } + if (startIndex >= locations.size()) { + return; + } + + boolean newSegment = startIndex == 0 || !locations.get(startIndex - 1).isValid(); + ArrayList lastSegmentPoints = new ArrayList(); + boolean useLastPolyline = true; + for (int i = startIndex; i < locations.size(); i++) { + CachedLocation cachedLocation = locations.get(i); + + // If not valid, start a new segment + if (!cachedLocation.isValid()) { + newSegment = true; + continue; + } + LatLng latLng = cachedLocation.getLatLng(); + if (newSegment) { + TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, color, useLastPolyline); + useLastPolyline = false; + newSegment = false; + } + lastSegmentPoints.add(latLng); + } + TrackPathUtils.addPath(googleMap, paths, lastSegmentPoints, color, useLastPolyline); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java deleted file mode 100644 index d29f86edf..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.maps; - -import com.google.android.apps.mytracks.MapOverlay.CachedLocation; -import com.google.android.maps.GeoPoint; -import com.google.android.maps.Projection; -import com.google.android.maps.mytracks.R; -import com.google.common.annotations.VisibleForTesting; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.Rect; - -import java.util.List; - -/** - * A path painter that not variates the path colors. - * - * @author Vangelis S. - */ -public class SingleColorTrackPathPainter implements TrackPathPainter { - private final Paint selectedTrackPaint; - private Path path; - - public SingleColorTrackPathPainter(Context context) { - selectedTrackPaint = TrackPathUtils.getPaint(context, R.color.red); - } - - @Override - public boolean hasPath() { - return path != null; - } - - @Override - public boolean updateState() { - return false; - } - - @Override - public void updatePath( - Projection projection, Rect viewRect, int startIndex, List points) { - if (!hasPath()) { - path = newPath(); - } - updatePath(projection, viewRect, startIndex, points, path); - } - - @Override - public void clearPath() { - path = null; - } - - @Override - public void drawPath(Canvas canvas) { - if (path != null) { - canvas.drawPath(path, selectedTrackPaint); - } - } - - /** - * Updates the path. - * - * @param projection the projection - * @param viewRect the view rectangle - * @param startIndex the start index - * @param points the points - * @param pathToUpdate the path to update - */ - @VisibleForTesting - void updatePath(Projection projection, Rect viewRect, int startIndex, List points, - Path pathToUpdate) { - pathToUpdate.incReserve(points.size() - startIndex); - - boolean hasLastPoint = startIndex != 0 && points.get(startIndex - 1).isValid(); - boolean newSegment = !hasLastPoint; - // Assume if last point exists, it is visible - boolean lastPointVisible = hasLastPoint; - Point point = new Point(); - - for (int i = startIndex; i < points.size(); i++) { - CachedLocation cachedLocation = points.get(i); - - // If not valid, start a new segment - if (!cachedLocation.isValid()) { - newSegment = true; - continue; - } - - GeoPoint geoPoint = cachedLocation.getGeoPoint(); - // Check if this breaks the existing segment. - boolean pointVisible = viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6()); - if (!pointVisible && !lastPointVisible) { - // This point and the last point are both outside visible area. - newSegment = true; - } - lastPointVisible = pointVisible; - - // Either update point or draw a line from the last point - projection.toPixels(geoPoint, point); - if (newSegment) { - pathToUpdate.moveTo(point.x, point.y); - newSegment = false; - } else { - pathToUpdate.lineTo(point.x, point.y); - } - } - } - - /** - * Creates a new path. - */ - @VisibleForTesting - protected Path newPath() { - return new Path(); - } - - /** - * Gets the path. - */ - @VisibleForTesting - public Path getPath() { - return path; - } - -} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPath.java similarity index 50% rename from MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java rename to MyTracks/src/com/google/android/apps/mytracks/maps/TrackPath.java index 2f6b31fde..050a1f705 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 Google Inc. + * Copyright 2012 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 @@ -17,50 +17,27 @@ package com.google.android.apps.mytracks.maps; import com.google.android.apps.mytracks.MapOverlay.CachedLocation; -import com.google.android.maps.Projection; - -import android.graphics.Canvas; -import android.graphics.Rect; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.Polyline; +import java.util.ArrayList; import java.util.List; /** - * An interface for classes which paint the track path. + * A track path. * - * @author Vangelis S. + * @author Jimmy Shih */ -public interface TrackPathPainter { +public interface TrackPath { - /** - * Returns true if has path. - */ - public boolean hasPath(); - - /** - * Updates state. Returns true if the state is updated. - */ public boolean updateState(); /** - * Updates the path. Creates a new path if necessary + * Updates the path. * - * @param projection the projection - * @param viewRect the view rectangle * @param startIndex the start index * @param points the points */ - public void updatePath( - Projection projection, Rect viewRect, int startIndex, List points); - - /** - * Clears the path. - */ - public void clearPath(); - - /** - * Draws the path. - * - * @param canvas the canvas - */ - public void drawPath(Canvas canvas); + public void updatePath(GoogleMap googleMap, ArrayList paths, int startIndex, + List points); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathFactory.java similarity index 65% rename from MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java rename to MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathFactory.java index 460d4b280..15a16a092 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathFactory.java @@ -16,42 +16,37 @@ package com.google.android.apps.mytracks.maps; -import static com.google.android.apps.mytracks.Constants.TAG; - import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.maps.mytracks.R; import android.content.Context; -import android.util.Log; /** - * A factory for {@link TrackPathPainter}. + * A factory for {@link TrackPath}. * * @author Vangelis S. */ -public class TrackPathPainterFactory { +public class TrackPathFactory { - private TrackPathPainterFactory() {} + private TrackPathFactory() {} /** - * Get a new {@link TrackPathPainter}. + * Get a new {@link TrackPath}. * * @param context the context */ - public static TrackPathPainter getTrackPathPainter(Context context) { + public static TrackPath getTrackPath(Context context) { String trackColorMode = PreferencesUtils.getString( context, R.string.track_color_mode_key, PreferencesUtils.TRACK_COLOR_MODE_DEFAULT); - Log.i(TAG, "Creating track path painter of type: " + trackColorMode); if (context.getString(R.string.settings_map_track_color_mode_dynamic_value) .equals(trackColorMode)) { - return new DynamicSpeedTrackPathPainter(context, new DynamicSpeedTrackPathDescriptor( - context)); + return new MultiColorTrackPath(context, new DynamicSpeedTrackPathDescriptor(context)); } else if (context.getString(R.string.settings_map_track_color_mode_fixed_value) .equals(trackColorMode)) { - return new DynamicSpeedTrackPathPainter(context, new FixedSpeedTrackPathDescriptor(context)); + return new MultiColorTrackPath(context, new FixedSpeedTrackPathDescriptor(context)); } else { - return new SingleColorTrackPathPainter(context); + return new SingleColorTrackPath(context); } } } \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtils.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtils.java index a9cb03cf0..1818a2262 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtils.java @@ -16,8 +16,12 @@ package com.google.android.apps.mytracks.maps; -import android.content.Context; -import android.graphics.Paint; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Model; +import com.google.android.gms.maps.model.Polyline; + +import java.util.ArrayList; /** * Various utility functions for track path painting. @@ -29,17 +33,32 @@ public class TrackPathUtils { private TrackPathUtils() {} /** - * Gets a paint. + * Add a path. * - * @param context the context - * @param colorId the color id + * @param googleMap the google map + * @param paths the existing paths + * @param points the path points + * @param color the path color + * @param append true to append to the last path */ - public static Paint getPaint(Context context, int colorId) { - Paint paint = new Paint(); - paint.setColor(context.getResources().getColor(colorId)); - paint.setStrokeWidth(3); - paint.setStyle(Paint.Style.STROKE); - paint.setAntiAlias(true); - return paint; + @SuppressWarnings("unchecked") + public static void addPath(GoogleMap googleMap, ArrayList paths, + ArrayList points, int color, boolean append) { + if (points.size() == 0) { + return; + } + if (append && paths.size() != 0) { + Polyline lastPolyline = paths.get(paths.size() - 1); + ArrayList pathPoints = new ArrayList(); + pathPoints.addAll(lastPolyline.getPoints()); + pathPoints.addAll(points); + lastPolyline.setPoints(pathPoints); + } else { + Polyline.Options options = Model.newPolylineOptions() + .addAll(points).width(5).color(color); + Polyline polyline = googleMap.addPolyline(options); + paths.add(polyline); + } + points.clear(); } } \ No newline at end of file diff --git a/MyTracksLib/.classpath b/MyTracksLib/.classpath index 0ca188f97..d3bb07b44 100644 --- a/MyTracksLib/.classpath +++ b/MyTracksLib/.classpath @@ -1,8 +1,8 @@ + - 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 76eb8b6ae..538170768 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java @@ -232,6 +232,11 @@ public interface MyTracksProviderUtils { */ public Location getLastValidTrackPoint(long trackId); + /** + * Gets the last valid location recorded. Returns null if it doesn't exist. + */ + public Location getLastValidTrackPoint(); + /** * Creates a location cursor. The caller owns the returned cursor and is * responsible for closing it. 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 30c63939c..6f45d3931 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -40,6 +40,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { private static final String TAG = MyTracksProviderUtilsImpl.class.getSimpleName(); + private static final int MAX_LATITUDE = 90000000; + private final ContentResolver contentResolver; private int defaultCursorBatchSize = 2000; @@ -74,7 +76,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { int mapIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAPID); int tableIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.TABLEID); int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON); - + Track track = new Track(); TripStatistics tripStatistics = track.getTripStatistics(); if (!cursor.isNull(idIndex)) { @@ -155,7 +157,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { public void deleteAllTracks() { contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null); contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null); - // Delete tracks last since it triggers a database vaccum call + // Delete tracks last since it triggers a database vaccum call contentResolver.delete(TracksColumns.CONTENT_URI, null, null); } @@ -164,15 +166,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { Track track = getTrack(trackId); if (track != null) { String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?"; - String[] selectionArgs = new String[] { Long.toString(track.getStartId()), Long.toString( - track.getStopId()) }; + String[] selectionArgs = new String[] { + Long.toString(track.getStartId()), Long.toString(track.getStopId()) }; contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs); } contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", new String[] { Long.toString(trackId) }); // Delete tracks last since it triggers a database vaccum call - contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[] { - Long.toString(trackId) }); + contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", + new String[] { Long.toString(trackId) }); } @Override @@ -326,9 +328,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); - + Waypoint waypoint = new Waypoint(); - + if (!cursor.isNull(idIndex)) { waypoint.setId(cursor.getLong(idIndex)); } @@ -362,7 +364,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { if (!cursor.isNull(stopIdIndex)) { waypoint.setStopId(cursor.getLong(stopIdIndex)); } - + Location location = new Location(""); if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) { location.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6); @@ -384,7 +386,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { location.setBearing(cursor.getFloat(bearingIndex)); } waypoint.setLocation(location); - + TripStatistics tripStatistics = new TripStatistics(); boolean hasTripStatistics = false; if (!cursor.isNull(startTimeIndex)) { @@ -427,7 +429,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); hasTripStatistics = true; } - + if (hasTripStatistics) { waypoint.setTripStatistics(tripStatistics); } @@ -485,8 +487,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=" + Waypoint.TYPE_STATISTICS; String[] selectionArgs = new String[] { Long.toString(trackId) }; - cursor = getWaypointCursor( - null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); + cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); if (cursor != null && cursor.moveToFirst()) { return createWaypoint(cursor); } @@ -585,7 +586,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { ContentValues createContentValues(Waypoint waypoint) { ContentValues values = new ContentValues(); - + // Value < 0 indicates no id is available if (waypoint.getId() >= 0) { values.put(WaypointsColumns._ID, waypoint.getId()); @@ -600,7 +601,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { values.put(WaypointsColumns.DURATION, waypoint.getDuration()); values.put(WaypointsColumns.STARTID, waypoint.getStartId()); values.put(WaypointsColumns.STOPID, waypoint.getStopId()); - + Location location = waypoint.getLocation(); if (location != null) { values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); @@ -619,7 +620,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { values.put(WaypointsColumns.BEARING, location.getBearing()); } } - + TripStatistics tripStatistics = waypoint.getTripStatistics(); if (tripStatistics != null) { values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime()); @@ -720,7 +721,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } return -1L; } - + @Override public long getLastTrackPointId(long trackId) { if (trackId < 0) { @@ -752,18 +753,26 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " - + TrackPointsColumns.LATITUDE + "<=90000000)"; + + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; String[] selectionArgs = new String[] { Long.toString(trackId) }; return findTrackPointBy(selection, selectionArgs); } + @Override + public Location getLastValidTrackPoint() { + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from " + + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.LATITUDE + "<=" + + MAX_LATITUDE + ")"; + return findTrackPointBy(selection, null); + } + @Override public Cursor getTrackPointCursor( long trackId, long startTrackPointId, int maxLocations, boolean descending) { if (trackId < 0) { return null; } - + String selection; String[] selectionArgs; if (startTrackPointId >= 0) { @@ -775,7 +784,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { selection = TrackPointsColumns.TRACKID + "=?"; selectionArgs = new String[] { Long.toString(trackId) }; } - + String sortOrder = TrackPointsColumns._ID; if (descending) { sortOrder += " DESC"; @@ -787,8 +796,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } @Override - public LocationIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, - final boolean descending, final LocationFactory locationFactory) { + public LocationIterator getTrackPointLocationIterator(final long trackId, + final long startTrackPointId, final boolean descending, + final LocationFactory locationFactory) { if (locationFactory == null) { throw new IllegalArgumentException("locationFactory is null"); } @@ -798,7 +808,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { private final CachedTrackPointsIndexes indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) : null; - + /** * Gets the track point cursor. * @@ -807,24 +817,24 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { private Cursor getCursor(long trackPointId) { return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); } - + /** * Advances the cursor to the next batch. Returns true if successful. */ private boolean advanceCursorToNextBatch() { - long trackPointId = lastTrackPointId == -1L ? -1L : lastTrackPointId + (descending ? -1 - : 1); + long trackPointId = lastTrackPointId == -1L ? -1L + : lastTrackPointId + (descending ? -1 : 1); Log.d(TAG, "Advancing track point id: " + trackPointId); cursor.close(); cursor = getCursor(trackPointId); return cursor != null; } - + @Override public long getLocationId() { return lastTrackPointId; } - + @Override public boolean hasNext() { if (cursor == null) { @@ -841,7 +851,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } return true; } - + @Override public Location next() { if (cursor == null) { @@ -857,7 +867,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { fillTrackPoint(cursor, indexes, location); return location; } - + @Override public void close() { if (cursor != null) { @@ -865,7 +875,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { cursor = null; } } - + @Override public void remove() { throw new UnsupportedOperationException(); @@ -890,7 +900,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { values.put(TrackPointsColumns.TRACKID, trackId); values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); - + // Hack for Samsung phones that don't properly populate the time field long time = location.getTime(); if (time == 0) { @@ -909,7 +919,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { if (location.hasBearing()) { values.put(TrackPointsColumns.BEARING, location.getBearing()); } - + if (location instanceof MyTracksLocation) { MyTracksLocation myTracksLocation = (MyTracksLocation) location; if (myTracksLocation.getSensorDataSet() != null) { @@ -928,7 +938,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { */ private void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) { location.reset(); - + if (!cursor.isNull(indexes.longitudeIndex)) { location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); } diff --git a/MyTracksTest/.classpath b/MyTracksTest/.classpath index 17b7b5ec6..a67371fb4 100644 --- a/MyTracksTest/.classpath +++ b/MyTracksTest/.classpath @@ -1,8 +1,6 @@ - - @@ -10,5 +8,7 @@ + + diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java deleted file mode 100644 index bcc24231f..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MapOverlayTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2010 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.Waypoint; -import com.google.android.apps.mytracks.maps.SingleColorTrackPathPainter; -import com.google.android.maps.MapView; - -import android.graphics.Canvas; -import android.graphics.Path; -import android.location.Location; -import android.test.AndroidTestCase; - -/** - * Tests {@link MapOverlay}. - * - * @author Bartlomiej Niechwiej - * @author Vangelis S. - */ -public class MapOverlayTest extends AndroidTestCase { - private Canvas canvas; - private MockMyTracksOverlay mockMyTracksOverlay; - private MapView mapView; - - @Override - protected void setUp() throws Exception { - super.setUp(); - canvas = new Canvas(); - mockMyTracksOverlay = new MockMyTracksOverlay(getContext()); - - // Enable drawing. - mockMyTracksOverlay.setTrackDrawingEnabled(true); - - // Set a TrackPathPainter with a MockPath. - mockMyTracksOverlay.setTrackPathPainter(new SingleColorTrackPathPainter(getContext()) { - @Override - public Path newPath() { - return new MockPath(); - } - }); - - mapView = null; - } - - /** - * Tests {@link MapOverlay#addLocation(Location)}. - */ - public void testAddLocation() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - location.setLongitude(20); - mockMyTracksOverlay.addLocation(location); - assertEquals(1, mockMyTracksOverlay.getNumLocations()); - assertEquals(0, mockMyTracksOverlay.getNumWaypoints()); - - location.setLatitude(20); - location.setLongitude(30); - mockMyTracksOverlay.addLocation(location); - assertEquals(2, mockMyTracksOverlay.getNumLocations()); - assertEquals(0, mockMyTracksOverlay.getNumWaypoints()); - assertFalse(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - - // Draw and make sure that we don't lose any point. - mockMyTracksOverlay.draw(canvas, mapView, false); - assertEquals(2, mockMyTracksOverlay.getNumLocations()); - assertEquals(0, mockMyTracksOverlay.getNumWaypoints()); - assertTrue(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - SingleColorTrackPathPainter trackPathPainter = (SingleColorTrackPathPainter) mockMyTracksOverlay - .getTrackPathPainter(); - MockPath path = (MockPath) trackPathPainter.getPath(); - assertEquals(2, path.getTotalPoints()); - - mockMyTracksOverlay.draw(canvas, mapView, true); - assertEquals(2, mockMyTracksOverlay.getNumLocations()); - assertEquals(0, mockMyTracksOverlay.getNumWaypoints()); - assertTrue(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - } - - /** - * Tests {@link MapOverlay#clearPoints()}. - */ - public void testClearPoints() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - location.setLongitude(20); - mockMyTracksOverlay.addLocation(location); - assertEquals(1, mockMyTracksOverlay.getNumLocations()); - mockMyTracksOverlay.clearPoints(); - assertEquals(0, mockMyTracksOverlay.getNumLocations()); - - // Test after drawing on canvas - final int locations = 100; - for (int i = 0; i < locations; ++i) { - mockMyTracksOverlay.addLocation(location); - } - assertEquals(locations, mockMyTracksOverlay.getNumLocations()); - mockMyTracksOverlay.draw(canvas, mapView, false); - mockMyTracksOverlay.draw(canvas, mapView, true); - mockMyTracksOverlay.clearPoints(); - assertEquals(0, mockMyTracksOverlay.getNumLocations()); - } - - /** - * Tests {@link MapOverlay#addWaypoint(Waypoint)}. - */ - public void testAddWaypoint() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - location.setLongitude(20); - Waypoint waypoint = new Waypoint(); - waypoint.setLocation(location); - mockMyTracksOverlay.addWaypoint(waypoint); - assertEquals(1, mockMyTracksOverlay.getNumWaypoints()); - assertEquals(0, mockMyTracksOverlay.getNumLocations()); - assertFalse(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - - final int waypoints = 10; - for (int i = 0; i < waypoints; ++i) { - waypoint = new Waypoint(); - waypoint.setLocation(location); - mockMyTracksOverlay.addWaypoint(waypoint); - } - assertEquals(1 + waypoints, mockMyTracksOverlay.getNumWaypoints()); - assertEquals(0, mockMyTracksOverlay.getNumLocations()); - assertFalse(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - } - - /** - * Tests {@link MapOverlay#clearWaypoints()}. - */ - public void testClearWaypoints() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - location.setLongitude(20); - Waypoint waypoint = new Waypoint(); - waypoint.setLocation(location); - mockMyTracksOverlay.addWaypoint(waypoint); - assertEquals(1, mockMyTracksOverlay.getNumWaypoints()); - mockMyTracksOverlay.clearWaypoints(); - assertEquals(0, mockMyTracksOverlay.getNumWaypoints()); - } - - /** - * Tests {@link MapOverlay#draw(Canvas, MapView, boolean)}. - */ - public void testDrawing() { - Location location = new Location("gps"); - location.setLatitude(10); - for (int i = 0; i < 40; ++i) { - location.setLongitude(20 + i); - Waypoint waypoint = new Waypoint(); - waypoint.setLocation(location); - mockMyTracksOverlay.addWaypoint(waypoint); - } - for (int i = 0; i < 100; ++i) { - location = new Location("gps"); - location.setLatitude(20 + i / 2); - location.setLongitude(150 - i); - mockMyTracksOverlay.addLocation(location); - } - - // Shadow. - mockMyTracksOverlay.draw(canvas, mapView, true); - // We don't expect to do anything if - assertFalse(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - assertEquals(40, mockMyTracksOverlay.getNumWaypoints()); - assertEquals(100, mockMyTracksOverlay.getNumLocations()); - - // No shadow. - mockMyTracksOverlay.draw(canvas, mapView, false); - assertTrue(mockMyTracksOverlay.getTrackPathPainter().hasPath()); - SingleColorTrackPathPainter trackPathPainter = (SingleColorTrackPathPainter) mockMyTracksOverlay - .getTrackPathPainter(); - MockPath path = (MockPath) trackPathPainter.getPath(); - assertEquals(40, mockMyTracksOverlay.getNumWaypoints()); - assertEquals(100, mockMyTracksOverlay.getNumLocations()); - assertEquals(100, path.getTotalPoints()); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MockMyTracksOverlay.java b/MyTracksTest/src/com/google/android/apps/mytracks/MockMyTracksOverlay.java deleted file mode 100644 index d9a393498..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MockMyTracksOverlay.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.maps.MapView; -import com.google.android.maps.Projection; - -import android.content.Context; -import android.graphics.Rect; - -/** - * A mock {@link MapOverlay} for testing. - * - * @author Bartlomiej Niechwiej - * @author Vangelis S. - * - */ -public class MockMyTracksOverlay extends MapOverlay { - - private Projection projection; - - public MockMyTracksOverlay(Context context) { - super(context); - projection = new MockProjection(); - } - - @Override - public Projection getMapProjection(MapView mapView) { - return projection; - } - - @Override - public Rect getMapViewRect(MapView mapView) { - return new Rect(0, 0, (int) (100 * 1E6), (int) (100 * 1E6)); - } -} \ No newline at end of file diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MockPath.java b/MyTracksTest/src/com/google/android/apps/mytracks/MockPath.java deleted file mode 100644 index 0a7ed511c..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MockPath.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 android.graphics.Path; -import android.graphics.PointF; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -import junit.framework.Assert; - -/** - * A mock {@link Path}. Records calls to {@link Path#lineTo(float, float)} and - * {@link Path#moveTo(float, float)}. - * - * @author Bartlomiej Niechwiej - * @author Vangelis S. - */ -public class MockPath extends Path { - - // A list of disjointed path segments. - private final List> segments = new LinkedList>(); - - // The total number of points in this path. - private int totalPoints; - private List currentSegment; - - @Override - public void lineTo(float x, float y) { - super.lineTo(x, y); - Assert.assertNotNull(currentSegment); - currentSegment.add(new PointF(x, y)); - totalPoints++; - } - - @Override - public void moveTo(float x, float y) { - super.moveTo(x, y); - currentSegment = new ArrayList(Arrays.asList(new PointF(x, y))); - segments.add(currentSegment); - totalPoints++; - } - - /** - * Gets the total number of points in this path. - */ - public int getTotalPoints() { - return totalPoints; - } - -} \ No newline at end of file diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/MockProjection.java b/MyTracksTest/src/com/google/android/apps/mytracks/MockProjection.java deleted file mode 100644 index b1dbc5bdd..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/MockProjection.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.maps.GeoPoint; -import com.google.android.maps.Projection; - -import android.graphics.Point; - -/** - * A mock {@link Projection}. Acts as an identity matrix. - * - * @author Bartlomiej Niechwiej - * @author Vangelis S. - * - */ -public class MockProjection implements Projection { - - @Override - public Point toPixels(GeoPoint geoPoint, Point point) { - return point; - } - - @Override - public float metersToEquatorPixels(float meters) { - return meters; - } - - @Override - public GeoPoint fromPixels(int x, int y) { - return new GeoPoint(y, x); - } -} \ No newline at end of file diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/endtoendtest/ViewsTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/endtoendtest/ViewsTest.java index 59e563924..ba266aa35 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/endtoendtest/ViewsTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/endtoendtest/ViewsTest.java @@ -16,7 +16,7 @@ package com.google.android.apps.mytracks.endtoendtest; import com.google.android.apps.mytracks.TrackListActivity; -import com.google.android.apps.mytracks.fragments.MapFragment; +import com.google.android.apps.mytracks.fragments.MyTracksMapFragment; import com.google.android.maps.MapView; import com.google.android.maps.mytracks.R; @@ -51,8 +51,8 @@ public class ViewsTest extends ActivityInstrumentationTestCase2 points = createCachedLocations(NUMBER_OF_LOCATIONS, INVALID_LATITUDE, -1); - dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), 1, points); - AndroidMock.verify(trackPathDescriptor); - // Should be zero for there is no valid locations. - assertEquals(0, dynamicSpeedTrackPathPainter.getColoredPaths().size()); - } - - /** - * Tests the - * {@link DynamicSpeedTrackPathPainter#updatePath(Projection, Rect, int, List)} - * when all locations are valid. - */ - public void testUpdatePath_AllValidLocation() { - List points = createCachedLocations(NUMBER_OF_LOCATIONS, - TrackStubUtils.INITIAL_LATITUDE, -1); - - // Gets a number as the start index of points. - int startLocationIdx = NUMBER_OF_LOCATIONS / 2; - dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, points); - - AndroidMock.verify(trackPathDescriptor); - assertEquals(NUMBER_OF_LOCATIONS - startLocationIdx, dynamicSpeedTrackPathPainter - .getColoredPaths().size()); - } - - /** - * Tests the - * {@link DynamicSpeedTrackPathPainter#updatePath(Projection, Rect, int, List)} - * when all locations are valid. This test setups 4 segments with 25 points - * each. The first segment has slow speed, the second segment has normal - * speed, the third segment has fast speed, and the fourth segment has slow - * speed. - */ - public void testUpdatePath_CheckColoredPath() { - // Gets the slow speed. Divide SLOW_SPEED by 2 to make it smaller than - // SLOW_SPEED. Speed in MyTracksLocation use MS, but speed in CachedLocation - // use KMH. - int slowSpeed = SLOW_SPEED_MS / 2; - // Gets the normal speed. Makes it smaller than SLOW_SPEED and bigger than - // NORMAL_SPEED. Speed in MyTracksLocation use MS, but speed in - // CachedLocation use KMH. - int normalSpeed = (SLOW_SPEED_MS + NORMAL_SPEED_MS) / 2; - // Gets the fast speed. Multiply it by 2 to make it bigger than - // NORMAL_SPEED. Speed in MyTracksLocation use MS, but speed in - // CachedLocation use KMH. - int fastSpeed = NORMAL_SPEED_MS * 2; - - // Get a number of startLocationIdx. And divide NUMBER_OF_LOCATIONS by 8 to - // make sure it is less than numberOfFirstThreeSegments. - int startLocationIdx = LOCATIONS_PER_SEGMENT / 2; - - List points = createCachedLocations(LOCATIONS_PER_SEGMENT, - TrackStubUtils.INITIAL_LATITUDE, slowSpeed); - points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, - normalSpeed)); - points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, - fastSpeed)); - points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, - slowSpeed)); - - dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, points); - - AndroidMock.verify(trackPathDescriptor); - List coloredPath = dynamicSpeedTrackPathPainter.getColoredPaths(); - assertEquals(NUMBER_OF_LOCATIONS - startLocationIdx, coloredPath.size()); - - // Checks different speeds with different color in the coloredPath. - for (int i = 0; i < NUMBER_OF_LOCATIONS - startLocationIdx; i++) { - if (i < LOCATIONS_PER_SEGMENT - startLocationIdx) { - // Slow. - assertEquals(getContext().getResources().getColor(R.color.slow_path), coloredPath.get(i) - .getPathPaint().getColor()); - } else if (i < LOCATIONS_PER_SEGMENT * 2 - startLocationIdx) { - // Normal. - assertEquals(getContext().getResources().getColor(R.color.normal_path), coloredPath.get(i) - .getPathPaint().getColor()); - } else if (i < LOCATIONS_PER_SEGMENT * 3 - startLocationIdx) { - // Fast. - assertEquals(getContext().getResources().getColor(R.color.fast_path), coloredPath.get(i) - .getPathPaint().getColor()); - } else { - // Slow. - assertEquals(getContext().getResources().getColor(R.color.slow_path), coloredPath.get(i) - .getPathPaint().getColor()); - } - } - } - - /** - * Initials a mocked TrackPathDescriptor object. - */ - @UsesMocks(TrackPathDescriptor.class) - private void initialTrackPathDescriptorMock() { - trackPathDescriptor = AndroidMock.createMock(TrackPathDescriptor.class); - AndroidMock.expect(trackPathDescriptor.getSlowSpeed()).andReturn(SLOW_SPEED_KMH); - AndroidMock.expect(trackPathDescriptor.getNormalSpeed()).andReturn(NORMAL_SPEED_KMH); - AndroidMock.replay(trackPathDescriptor); - } - -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java deleted file mode 100644 index fa59e216b..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2012 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.maps; - -import com.google.android.apps.mytracks.MapOverlay.CachedLocation; -import com.google.android.apps.mytracks.TrackStubUtils; -import com.google.android.maps.Projection; -import com.google.android.testing.mocking.AndroidMock; -import com.google.android.testing.mocking.UsesMocks; - -import android.graphics.Path; -import android.graphics.Rect; - -import java.util.List; - -/** - * Tests for the {@link SingleColorTrackPathPainter}. - * - * @author Youtao Liu - */ -public class SingleColorTrackPathPainterTest extends TrackPathPainterTestCase { - - private static final int NUMBER_OF_LOCATIONS = 10; - private SingleColorTrackPathPainter singleColorTrackPathPainter; - private Path pathMock; - - - /** - * Initials a mocked TrackPathDescriptor object and - * singleColorTrackPathPainter. - */ - @Override - @UsesMocks(Path.class) - protected void setUp() throws Exception { - super.setUp(); - - singleColorTrackPathPainter = new SingleColorTrackPathPainter(getContext()); - pathMock = AndroidMock.createStrictMock(Path.class); - } - - /** - * Tests the - * {@link SingleColorTrackPathPainter#updatePath(Projection, Rect, int, List)} - * method when all locations are valid. - */ - public void testUpdatePath_AllValidLocation() { - // Gets a number as the start index of points. - int startLocationIdx = NUMBER_OF_LOCATIONS / 2; - - pathMock.incReserve(NUMBER_OF_LOCATIONS - startLocationIdx); - List points = createCachedLocations(NUMBER_OF_LOCATIONS, - TrackStubUtils.INITIAL_LATITUDE, -1); - - for (int i = startLocationIdx; i < NUMBER_OF_LOCATIONS; i++) { - pathMock.lineTo(0, 0); - } - - AndroidMock.replay(pathMock); - singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, points, pathMock); - AndroidMock.verify(pathMock); - } - - /** - * Tests the - * {@link SingleColorTrackPathPainter#updatePath(Projection, Rect, int, List)} - * method when all locations are invalid. - */ - public void testUpdatePath_AllInvalidLocation() { - int startLocationIdx = NUMBER_OF_LOCATIONS / 2; - pathMock.incReserve(NUMBER_OF_LOCATIONS - startLocationIdx); - List points = createCachedLocations(NUMBER_OF_LOCATIONS, INVALID_LATITUDE, -1); - // Gets a random number from 1 to numberOfLocations. - AndroidMock.replay(pathMock); - singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, points, pathMock); - AndroidMock.verify(pathMock); - } - - /** - * Tests the - * {@link SingleColorTrackPathPainter#updatePath(Projection, Rect, int, List)} - * method when there are three segments. - */ - public void testUpdatePath_ThreeSegments() { - // First segment. - List points = createCachedLocations(NUMBER_OF_LOCATIONS, - TrackStubUtils.INITIAL_LATITUDE, -1); - points.addAll(createCachedLocations(1, INVALID_LATITUDE, -1)); - // Second segment. - points.addAll(createCachedLocations(NUMBER_OF_LOCATIONS, TrackStubUtils.INITIAL_LATITUDE, -1)); - points.addAll(createCachedLocations(1, INVALID_LATITUDE, -1)); - // Third segment. - points.addAll(createCachedLocations(NUMBER_OF_LOCATIONS, TrackStubUtils.INITIAL_LATITUDE, -1)); - // Gets a random number from 1 to numberOfLocations. - int startLocationIdx = NUMBER_OF_LOCATIONS / 2; - pathMock.incReserve(NUMBER_OF_LOCATIONS *3 + 1 +1 - startLocationIdx); - for (int i = 0; i < NUMBER_OF_LOCATIONS - startLocationIdx; i++) { - pathMock.lineTo(0, 0); - } - pathMock.moveTo(0, 0); - for (int i = 0; i < NUMBER_OF_LOCATIONS - 1; i++) { - pathMock.lineTo(0, 0); - } - pathMock.moveTo(0, 0); - for (int i = 0; i < NUMBER_OF_LOCATIONS - 1; i++) { - pathMock.lineTo(0, 0); - } - - AndroidMock.replay(pathMock); - singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, points, pathMock); - AndroidMock.verify(pathMock); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorDynamicSpeedTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorDynamicSpeedTest.java deleted file mode 100644 index ec3361dbf..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorDynamicSpeedTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.maps; - -import android.location.Location; - -/** - * Tests for the MyTracks track path descriptors and painters. - * - * @author Vangelis S. - */ -public class TrackPathDescriptorDynamicSpeedTest extends TrackPathPainterTestCase { - - public void testDynamicSpeedTrackPathDescriptor() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - - for (int i = 0; i < 100; ++i) { - location = new Location("gps"); - location.setLatitude(20 + i / 2); - location.setLongitude(150 - i); - myTracksOverlay.addLocation(location); - } - - TrackPathPainter painter = new DynamicSpeedTrackPathPainter( - getContext(), new DynamicSpeedTrackPathDescriptor(getContext())); - myTracksOverlay.setTrackPathPainter(painter); - - int startLocationIdx = 0; - - assertNotNull(painter); - painter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, - myTracksOverlay.getPoints()); - assertNotNull(myTracksOverlay.getTrackPathPainter().hasPath()); - painter.drawPath(canvas); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorFixedSpeedTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorFixedSpeedTest.java deleted file mode 100644 index 668d1d741..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathDescriptorFixedSpeedTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.maps; - -import android.location.Location; - -/** - * Tests for the MyTracks track path descriptors and painters. - * - * @author Vangelis S. - */ -public class TrackPathDescriptorFixedSpeedTest extends TrackPathPainterTestCase { - - public void testFixedSpeedTrackPathDescriptor() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - - for (int i = 0; i < 100; ++i) { - location = new Location("gps"); - location.setLatitude(20 + i / 2); - location.setLongitude(150 - i); - myTracksOverlay.addLocation(location); - } - - TrackPathPainter painter = new DynamicSpeedTrackPathPainter( - getContext(), new FixedSpeedTrackPathDescriptor(getContext())); - myTracksOverlay.setTrackPathPainter(painter); - - int startLocationIdx = 0; - - assertNotNull(painter); - painter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, myTracksOverlay.getPoints()); - assertNotNull(myTracksOverlay.getTrackPathPainter().hasPath()); - painter.drawPath(canvas); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactoryTest.java deleted file mode 100644 index 9d9622b6a..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactoryTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.maps; - -import com.google.android.apps.mytracks.util.PreferencesUtils; -import com.google.android.maps.mytracks.R; - -import android.content.Context; -import android.location.Location; - -/** - * Tests for the MyTracks track path painter factory. - * - * @author Vangelis S. - */ -public class TrackPathPainterFactoryTest extends TrackPathPainterTestCase { - - public void testTrackPathPainterFactory() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - - for (int i = 0; i < 100; ++i) { - location = new Location("gps"); - location.setLatitude(20 + i / 2); - location.setLongitude(150 - i); - myTracksOverlay.addLocation(location); - } - - Context context = getContext(); - - testTrackPathPainterFactorySpecific(context, - R.string.settings_map_track_color_mode_single_value, SingleColorTrackPathPainter.class); - testTrackPathPainterFactorySpecific(context, R.string.settings_map_track_color_mode_fixed_value, - DynamicSpeedTrackPathPainter.class); - testTrackPathPainterFactorySpecific(context, - R.string.settings_map_track_color_mode_dynamic_value, DynamicSpeedTrackPathPainter.class); - } - - private void testTrackPathPainterFactorySpecific( - Context context, int track_color_mode, Class c) { - PreferencesUtils.setString( - context, R.string.track_color_mode_key, context.getString(track_color_mode)); - - int startLocationIdx = 0; - - TrackPathPainter painter = TrackPathPainterFactory.getTrackPathPainter(context); - myTracksOverlay.setTrackPathPainter(painter); - - assertNotNull(painter); - assertTrue(c.isInstance(painter)); - - painter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, - myTracksOverlay.getPoints()); - assertNotNull(myTracksOverlay.getTrackPathPainter().hasPath()); - painter.drawPath(canvas); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterSingleColorTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterSingleColorTest.java deleted file mode 100644 index 32cce7438..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterSingleColorTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.maps; - -import android.location.Location; - -/** - * Tests for the MyTracks track path descriptors and painters. - * - * @author Vangelis S. - */ -public class TrackPathPainterSingleColorTest extends TrackPathPainterTestCase { - - public void testSimpeColorTrackPathPainter() throws Exception { - Location location = new Location("gps"); - location.setLatitude(10); - - for (int i = 0; i < 100; ++i) { - location = new Location("gps"); - location.setLatitude(20 + i / 2); - location.setLongitude(150 - i); - myTracksOverlay.addLocation(location); - } - - TrackPathPainter painter = new SingleColorTrackPathPainter(getContext()); - myTracksOverlay.setTrackPathPainter(painter); - - int startLocationIdx = 0; - - assertNotNull(painter); - painter.updatePath(myTracksOverlay.getMapProjection(mockView), - myTracksOverlay.getMapViewRect(mockView), startLocationIdx, - myTracksOverlay.getPoints()); - assertNotNull(myTracksOverlay.getTrackPathPainter().hasPath()); - painter.drawPath(canvas); - } -} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java deleted file mode 100644 index b53909b6e..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.maps; - -import com.google.android.apps.mytracks.MapOverlay; -import com.google.android.apps.mytracks.MapOverlay.CachedLocation; -import com.google.android.apps.mytracks.MockMyTracksOverlay; -import com.google.android.apps.mytracks.TrackStubUtils; -import com.google.android.maps.MapView; - -import android.graphics.Canvas; -import android.location.Location; -import android.test.AndroidTestCase; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tests for the MyTracks track path descriptors and painters. - * - * @author Vangelis S. - */ -public class TrackPathPainterTestCase extends AndroidTestCase { - - protected Canvas canvas; - protected MockMyTracksOverlay myTracksOverlay; - protected MapView mockView; - - final int INVALID_LATITUDE = 100; - - @Override - protected void setUp() throws Exception { - super.setUp(); - canvas = new Canvas(); - myTracksOverlay = new MockMyTracksOverlay(getContext()); - // Enable drawing. - myTracksOverlay.setTrackDrawingEnabled(true); - mockView = null; - } - - /** - * Creates a list of CachedLocations. - * - * @param number the number of locations - * @param latitude the latitude value of locations. - * @param speed the speed(meter per second) of locations, and will give a default valid value if - * less than zero - * @return the simulated locations - */ - List createCachedLocations(int number, double latitude, float speed) { - List points = new ArrayList(); - for (int i = 0; i < number; ++i) { - Location location = TrackStubUtils.createMyTracksLocation(latitude, - TrackStubUtils.INITIAL_LONGITUDE, TrackStubUtils.INITIAL_ALTITUDE); - if (speed > 0) { - location.setSpeed(speed); - } - CachedLocation cachedLocation = new CachedLocation(location); - points.add(cachedLocation); - } - return points; - } -} diff --git a/google_play_services_lib/.classpath b/google_play_services_lib/.classpath new file mode 100644 index 000000000..3f9691c5d --- /dev/null +++ b/google_play_services_lib/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/google_play_services_lib/.project b/google_play_services_lib/.project new file mode 100755 index 000000000..0a85af4e7 --- /dev/null +++ b/google_play_services_lib/.project @@ -0,0 +1,33 @@ + + + Google Maps Library Dynamic + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/google_play_services_lib/AndroidManifest.xml b/google_play_services_lib/AndroidManifest.xml new file mode 100755 index 000000000..5a6bc5dc7 --- /dev/null +++ b/google_play_services_lib/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/google_play_services_lib/README.txt b/google_play_services_lib/README.txt new file mode 100644 index 000000000..901da746e --- /dev/null +++ b/google_play_services_lib/README.txt @@ -0,0 +1,14 @@ +Library Project including Google Play services client jar. + +This can be used by an Android project to use the API's provided +by Google Play services. + +There is technically no source, but the src folder is necessary +to ensure that the build system works. The content is actually +located in libs/google-play-services.jar + + +USAGE: + +Make sure you import this Android library project into your IDE +and set this project as a dependency. diff --git a/google_play_services_lib/libs/google-play-services.jar b/google_play_services_lib/libs/google-play-services.jar new file mode 100644 index 000000000..81c3c56ef Binary files /dev/null and b/google_play_services_lib/libs/google-play-services.jar differ diff --git a/google_play_services_lib/libs/google-play-services.jar.properties b/google_play_services_lib/libs/google-play-services.jar.properties new file mode 100644 index 000000000..429687b79 --- /dev/null +++ b/google_play_services_lib/libs/google-play-services.jar.properties @@ -0,0 +1 @@ +doc=../../../docs/reference diff --git a/google_play_services_lib/project.properties b/google_play_services_lib/project.properties new file mode 100755 index 000000000..db721fd89 --- /dev/null +++ b/google_play_services_lib/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-8 +android.library=true diff --git a/google_play_services_lib/res/values/maps_attrs.xml b/google_play_services_lib/res/values/maps_attrs.xml new file mode 100644 index 000000000..ade282e99 --- /dev/null +++ b/google_play_services_lib/res/values/maps_attrs.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google_play_services_lib/res/values/strings.xml b/google_play_services_lib/res/values/strings.xml new file mode 100644 index 000000000..5bb77e14a --- /dev/null +++ b/google_play_services_lib/res/values/strings.xml @@ -0,0 +1,47 @@ + + + + + Get Google Play services + + + This app won\'t run without + Google Play services, which are missing from your phone. + + + This app won\'t run without + Google Play services, which are missing from your tablet. + + + Get Google Play services + + + Enable Google Play services + + + This app won\'t work unless you + enable Google Play services. + + + Enable Google Play services + + + Update Google Play services + + + This app won\'t run unless you + update Google Play services. + + + Update + + diff --git a/google_play_services_lib/src/.readme b/google_play_services_lib/src/.readme new file mode 100644 index 000000000..145ea525b --- /dev/null +++ b/google_play_services_lib/src/.readme @@ -0,0 +1 @@ +This hidden file is here to ensure there is a src folder.