Update to map view v2.

This commit is contained in:
Jimmy Shih
2012-10-20 16:18:50 -07:00
parent 2a676dbc9a
commit 9eca3df172
70 changed files with 1291 additions and 2401 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
+1 -1
View File
@@ -3,7 +3,7 @@
<name>MyTracks</name>
<comment></comment>
<projects>
<project>MyTracksLib</project>
<project>MyTracks</project>
</projects>
<buildSpec>
<buildCommand>
+11 -3
View File
@@ -16,8 +16,8 @@ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
android:versionCode="56"
android:versionName="2.0.2"
android:versionCode="57"
android:versionName="2.0.3"
package="com.google.android.maps.mytracks" >
<!-- Permission to initialize services -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@@ -41,6 +41,7 @@ limitations under the License.
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.local" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.wise" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.writely" />
<!-- Permission to access Google services to communicate with the map servers. -->
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
@@ -79,9 +80,12 @@ limitations under the License.
android:permissionGroup="android.permission-group.LOCATION"
android:protectionLevel="dangerous" />
<uses-sdk
android:minSdkVersion="7"
android:minSdkVersion="8"
android:targetSdkVersion="16" />
<!-- Required features -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-feature
android:name="android.hardware.location"
android:required="true" />
@@ -130,6 +134,10 @@ limitations under the License.
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
<!-- For map view v2 -->
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyCKg_WrcnfP0I105X1CGHoiJsxT7w45XEI" />
<!-- Activities -->
<activity
android:label="@string/menu_aggregated_statistics"
+2
View File
@@ -86,6 +86,8 @@
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
-dontwarn com.google.common.collect.MinMaxPriorityQueue
-keep class com.google.android.gms.** { *; }
#
# Section 3
# My Tracks specific proguard settings.
+1
View File
@@ -13,3 +13,4 @@ proguard.config=proguard.cfg
# Project target.
target=Google Inc.:Google APIs:16
android.library.reference.1=../MyTracksLib
android.library.reference.2=../google_play_services_lib
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

+2 -10
View File
@@ -15,17 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map_container"
android:layout_height="fill_parent"
android:layout_width="fill_parent" >
<!-- apiKey corresponds to certificate md5: 81:DF:A2:63:53:C7:6B:1E:6C:24:62:D4:91:1C:36:3A -->
<com.google.android.maps.MapView
android:apiKey="0TXoRmxTinrZKQ9JSazj-9A34XX6_lXsRvTpXWA"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:id="@+id/map_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent" />
<TextView
android:background="@color/warning_background"
android:drawableLeft="@drawable/warning"
@@ -41,8 +33,8 @@ limitations under the License.
android:background="@drawable/my_location"
android:contentDescription="@string/icon_my_location"
android:id="@+id/map_my_location"
android:layout_below="@id/map_message"
android:layout_alignParentRight="true"
android:layout_below="@id/map_message"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
+15 -3
View File
@@ -16,8 +16,20 @@ limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:icon="@drawable/menu_satellite_mode"
android:id="@+id/map_satellite_mode"
android:icon="@drawable/menu_map_layer"
android:orderInCategory="5"
android:showAsAction="ifRoom"/>
android:showAsAction="ifRoom"
android:title="@string/menu_map_layer">
<menu>
<item
android:id="@+id/menu_map"
android:title="@string/menu_map"/>
<item
android:id="@+id/menu_satellite"
android:title="@string/menu_satellite"/>
<item
android:id="@+id/menu_terrain"
android:title="@string/menu_terrain"/>
</menu>
</item>
</menu>
-1
View File
@@ -16,7 +16,6 @@ limitations under the License.
-->
<resources>
<color name="black">#FF000000</color>
<color name="blue">#FF0000FF</color>
<color name="gray">#FF444444</color>
<color name="orange">#FFFAAD2C</color>
<color name="red">#FFBE161B</color>
+21 -6
View File
@@ -1157,11 +1157,18 @@ limitations under the License.
Character limit: ~20 (1 line)">
Insert marker
</string>
<string name="menu_map_mode"
translation_description="In the menu, to switch the display to the map mode.
<string name="menu_map"
translation_description="In the menu, under the map layer option, the submenu to display normal
map.
Character limit: ~20 (1 line)">
Map mode
Map
</string>
<string name="menu_map_layer"
translation_description="In the menu, to change the map layer.
Character limit: ~20 (1 line)">
Map layer
</string>
<string name="menu_markers"
translation_description="In the menu, to show a list of markers for the current track.
@@ -1175,8 +1182,9 @@ limitations under the License.
Character limit: ~20 (1 line)">
Play
</string>
<string name="menu_satellite_mode"
translation_description="In the menu, to switch the display to the satellite mode.
<string name="menu_satellite"
translation_description="In the menu, under the map layer option, the submenu to display
satellite.
Character limit: ~20 (1 line)">
Satellite
@@ -1256,6 +1264,13 @@ limitations under the License.
Character limit: ~20 (1 line)">
Stop GPS
</string>
<string name="menu_terrain"
translation_description="In the menu, under the map layer option, the submenu to display
terrain.
Character limit: ~20 (1 line)">
Terrain
</string>
<string name="menu_voice_frequency"
translation_description="In the menu, to configure the voice frequency, how frequent to announce
status.
@@ -2298,7 +2313,7 @@ limitations under the License.
translation_description="Title of the page to configure the My Tracks widget items.">
Configure Widget Items
</string>
<!-- Unit -->
<string name="unit_feet"
translation_description="Abbreviation for feet. E.g., '2 ft'.">
@@ -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);
@@ -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.
*/
@@ -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<CachedLocation> locations;
private final BlockingQueue<CachedLocation> pendingLocations;
private final List<Waypoint> waypoints;
private final List<CachedLocation> points;
private final BlockingQueue<CachedLocation> 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<Waypoint>();
this.points = new ArrayList<CachedLocation>(1024);
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
this.locations = new ArrayList<CachedLocation>(INITIAL_LOCATIONS_SIZE);
this.pendingLocations = new ArrayBlockingQueue<CachedLocation>(
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.
* <p>
* 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<CachedLocation> 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<Polyline> 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;
}
}
@@ -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) {
@@ -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<Overlay> 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);
}
}
}
}
@@ -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<Polyline> paths = new ArrayList<Polyline>();
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);
}
}
@@ -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<ColoredPath> 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<ColoredPath>();
}
@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<CachedLocation> 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<ColoredPath> getColoredPaths() {
return coloredPaths;
}
}
@@ -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<Polyline> paths, int startIndex,
List<CachedLocation> 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<LatLng> lastSegmentPoints = new ArrayList<LatLng>();
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;
}
}
}
@@ -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<Polyline> paths, int startIndex,
List<CachedLocation> locations) {
if (googleMap == null) {
return;
}
if (startIndex >= locations.size()) {
return;
}
boolean newSegment = startIndex == 0 || !locations.get(startIndex - 1).isValid();
ArrayList<LatLng> lastSegmentPoints = new ArrayList<LatLng>();
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);
}
}
@@ -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<CachedLocation> 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<CachedLocation> 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;
}
}
@@ -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<CachedLocation> 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<Polyline> paths, int startIndex,
List<CachedLocation> points);
}
@@ -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);
}
}
}
@@ -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<Polyline> paths,
ArrayList<LatLng> points, int color, boolean append) {
if (points.size() == 0) {
return;
}
if (append && paths.size() != 0) {
Polyline lastPolyline = paths.get(paths.size() - 1);
ArrayList<LatLng> pathPoints = new ArrayList<LatLng>();
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();
}
}
+1 -1
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -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.
@@ -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);
}
+2 -2
View File
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="src" path=".apt_generated">
<attributes>
<attribute name="optional" value="true"/>
@@ -10,5 +8,7 @@
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/MyTracks"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
@@ -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());
}
}
@@ -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));
}
}
@@ -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<List<PointF>> segments = new LinkedList<List<PointF>>();
// The total number of points in this path.
private int totalPoints;
private List<PointF> 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<PointF>(Arrays.asList(new PointF(x, y)));
segments.add(currentSegment);
totalPoints++;
}
/**
* Gets the total number of points in this path.
*/
public int getTotalPoints() {
return totalPoints;
}
}
@@ -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);
}
}
@@ -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<TrackListActivit
}
/**
* Switches view from {@link MapFragment} to @ ChartFragment} , then changes
* to @ StatsFragment} . Finally back to {@link MapFragment}. And check some
* Switches view from {@link MyTracksMapFragment} to @ ChartFragment} , then changes
* to @ StatsFragment} . Finally back to {@link MyTracksMapFragment}. And check some
* menus in these views. In MapFragment, the menu should contain satellite/map
* mode. In ChartFragment and StatsFragment, the menu should not contain
* satellite/map mode.
@@ -1,168 +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.ColoredPath;
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.maps.mytracks.R;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import android.graphics.Rect;
import java.util.List;
/**
* Tests for the {@link DynamicSpeedTrackPathPainter}.
*
* @author Youtao Liu
*/
public class DynamicSpeedTrackPathPainterTest extends TrackPathPainterTestCase {
private DynamicSpeedTrackPathPainter dynamicSpeedTrackPathPainter;
private TrackPathDescriptor trackPathDescriptor;
// This number must bigger than 10 to meet the requirement of test.
private static final int NUMBER_OF_LOCATIONS = 100;
private static final int LOCATIONS_PER_SEGMENT = 25;
// The maximum speed(KM/H) which is considered slow.
private static final int SLOW_SPEED_KMH = 30;
// The maximum speed(KM/H) which is considered normal.
private static final int NORMAL_SPEED_KMH = 50;
// Convert from kilometers per hour to meters per second
private static final double KMH_TO_MS = 1 / 3.6;
private static final int SLOW_SPEED_MS = (int) (SLOW_SPEED_KMH * KMH_TO_MS);
private static final int NORMAL_SPEED_MS = (int) (NORMAL_SPEED_KMH * KMH_TO_MS);
@Override
protected void setUp() throws Exception {
super.setUp();
initialTrackPathDescriptorMock();
dynamicSpeedTrackPathPainter = new DynamicSpeedTrackPathPainter(getContext(),
trackPathDescriptor);
}
/**
* Tests the method
* {@link DynamicSpeedTrackPathPainter#updatePath(Projection, Rect, int, List)}
* when all locations are invalid.
*/
public void testUpdatePath_AllInvalidLocation() {
List<CachedLocation> 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<CachedLocation> 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<CachedLocation> 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> 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);
}
}
@@ -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<CachedLocation> 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<CachedLocation> 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<CachedLocation> 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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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 <T> 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);
}
}
@@ -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);
}
}
@@ -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<CachedLocation> createCachedLocations(int number, double latitude, float speed) {
List<CachedLocation> points = new ArrayList<MapOverlay.CachedLocation>();
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;
}
}
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Google Maps Library Dynamic</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms"
android:versionCode="2000000"
android:versionName="2.0.00-000" >
<uses-sdk android:minSdkVersion="8"/>
</manifest>
+14
View File
@@ -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.
@@ -0,0 +1 @@
doc=../../../docs/reference
+15
View File
@@ -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
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2012 Google Inc. All Rights Reserved. -->
<resources>
<declare-styleable name="MapAttrs">
<attr name="mapType" format="enum">
<enum name="none" value="0"/>
<enum name="normal" value="1"/>
<enum name="satellite" value="2"/>
<enum name="terrain" value="3"/>
</attr>
<attr name="cameraBearing" format="float"/>
<attr name="cameraTargetLat" format="float"/>
<attr name="cameraTargetLng" format="float"/>
<attr name="cameraTilt" format="float"/>
<attr name="cameraZoom" format="float"/>
<attr name="uiCompass" format="boolean"/>
<attr name="uiRotateGestures" format="boolean"/>
<attr name="uiScrollGestures" format="boolean"/>
<attr name="uiTiltGestures" format="boolean"/>
<attr name="uiZoomControls" format="boolean"/>
<attr name="uiZoomGestures" format="boolean"/>
<attr name="useViewLifecycle" format="boolean"/>
<attr name="zOrderOnTop" format="boolean"/>
</declare-styleable>
</resources>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title of confirmation dialog informing user that they need to install
Google Play services (from Play Store) [CHAR LIMIT=40] -->
<string name="common_google_play_services_install_title">Get Google Play services</string>
<!-- (For phones) Message in confirmation dialog informing user that
they need to install Google Play services (from Play Store) [CHAR LIMIT=NONE] -->
<string name="common_google_play_services_install_text_phone">This app won\'t run without
Google Play services, which are missing from your phone.</string>
<!-- (For tablets) Message in confirmation dialog informing user that
they need to install Google Play services (from Play Store) [CHAR LIMIT=NONE] -->
<string name="common_google_play_services_install_text_tablet">This app won\'t run without
Google Play services, which are missing from your tablet.</string>
<!-- Button in confirmation dialog for installing Google Play services [CHAR LIMIT=30] -->
<string name="common_google_play_services_install_button">Get Google Play services</string>
<!-- Title of confirmation dialog informing user they need to enable
Google Play services in application settings [CHAR LIMIT=40] -->
<string name="common_google_play_services_enable_title">Enable Google Play services</string>
<!-- Message in confirmation dialog informing user they need to enable
Google Play services in application settings [CHAR LIMIT=NONE] -->
<string name="common_google_play_services_enable_text">This app won\'t work unless you
enable Google Play services.</string>
<!-- Button in confirmation dialog to enable Google Play services. Clicking it
will direct user to application settings of Google Play services where they
can enable it [CHAR LIMIT=30] -->
<string name="common_google_play_services_enable_button">Enable Google Play services</string>
<!-- Title of confirmation dialog informing user that they need to update
Google Play services (from Play Store) [CHAR LIMIT=40] -->
<string name="common_google_play_services_update_title">Update Google Play services</string>
<!-- Message in confirmation dialog informing user that they need to update
Google Play services (from Play Store) [CHAR LIMIT=NONE] -->
<string name="common_google_play_services_update_text">This app won\'t run unless you
update Google Play services.</string>
<!-- Button in confirmation dialog for updating Google Play services [CHAR LIMIT=20] -->
<string name="common_google_play_services_update_button">Update</string>
</resources>
+1
View File
@@ -0,0 +1 @@
This hidden file is here to ensure there is a src folder.