Update to map view v2.
@@ -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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<name>MyTracks</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>MyTracksLib</project>
|
||||
<project>MyTracks</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 671 B |
|
Before Width: | Height: | Size: 680 B |
|
Before Width: | Height: | Size: 738 B |
|
Before Width: | Height: | Size: 783 B |
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 671 B |
|
Before Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 694 B |
|
Before Width: | Height: | Size: 725 B |
|
Before Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 760 B |
|
Before Width: | Height: | Size: 707 B |
|
Before Width: | Height: | Size: 743 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 58 KiB |
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
This hidden file is here to ensure there is a src folder.
|
||||