diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main')
43 files changed, 2561 insertions, 1063 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml index 1b8b54c86f..0307d462c8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml @@ -5,5 +5,6 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> </manifest> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerOptions.java new file mode 100644 index 0000000000..5b1eebdb1b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/BaseMarkerOptions.java @@ -0,0 +1,38 @@ +package com.mapbox.mapboxsdk.annotations; + +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +public abstract class BaseMarkerOptions<U extends Marker, T extends BaseMarkerOptions<U, T>> implements Parcelable { + + protected LatLng position; + protected String snippet; + protected String title; + protected Icon icon; + + public T position(LatLng position) { + this.position = position; + return getThis(); + } + + public T snippet(String snippet) { + this.snippet = snippet; + return getThis(); + } + + public T title(String title) { + this.title = title; + return getThis(); + } + + public T icon(Icon icon) { + this.icon = icon; + return getThis(); + } + + public abstract T getThis(); + + public abstract U getMarker(); + +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java index bee4ec47c5..d7d41b98be 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/IconFactory.java @@ -150,4 +150,8 @@ public final class IconFactory { } return fromInputStream(is); } + + public static Icon recreate(@NonNull String iconId, @NonNull Bitmap bitmap) { + return new Icon(iconId, bitmap); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java index 9c79ffcbd9..d8763e321d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/InfoWindow.java @@ -53,22 +53,35 @@ public class InfoWindow { mIsVisible = false; mView = new WeakReference<>(view); - // default behavior: close it when clicking on the tooltip: - view.setOnTouchListener(new View.OnTouchListener() { + view.setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_UP) { + public void onClick(View v) { + MapboxMap mapboxMap = mMapboxMap.get(); + if (mapboxMap != null) { + MapboxMap.OnInfoWindowClickListener onInfoWindowClickListener = mapboxMap.getOnInfoWindowClickListener(); boolean handledDefaultClick = false; - MapboxMap.OnInfoWindowClickListener onInfoWindowClickListener = - mMapboxMap.get().getOnInfoWindowClickListener(); if (onInfoWindowClickListener != null) { - handledDefaultClick = onInfoWindowClickListener.onMarkerClick(getBoundMarker()); + handledDefaultClick = onInfoWindowClickListener.onInfoWindowClick(getBoundMarker()); } if (!handledDefaultClick) { + // default behavior: close it when clicking on the tooltip: close(); } } + } + }); + + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + MapboxMap mapboxMap = mMapboxMap.get(); + if (mapboxMap != null) { + MapboxMap.OnInfoWindowLongClickListener listener = mapboxMap.getOnInfoWindowLongClickListener(); + if (listener != null) { + listener.onInfoWindowLongClick(getBoundMarker()); + } + } return true; } }); @@ -179,11 +192,10 @@ public class InfoWindow { if (mIsVisible) { mIsVisible = false; View view = mView.get(); - if (view != null) { + if (view != null && view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); - setBoundMarker(null); - onClose(); } + onClose(); } return this; } @@ -210,7 +222,12 @@ public class InfoWindow { private void onClose() { MapboxMap mapboxMap = mMapboxMap.get(); if (mapboxMap != null) { + MapboxMap.OnInfoWindowCloseListener listener = mapboxMap.getOnInfoWindowCloseListener(); + if (listener != null) { + listener.onInfoWindowClose(getBoundMarker()); + } mapboxMap.deselectMarker(getBoundMarker()); + setBoundMarker(null); } } @@ -237,4 +254,8 @@ public class InfoWindow { } } + boolean isVisible() { + return mIsVisible; + } + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java index 27c9c03697..c2683cbb56 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/Marker.java @@ -15,7 +15,7 @@ import com.mapbox.mapboxsdk.maps.MapView; * An {@link InfoWindow} can be shown when a Marker is pressed * <p/> */ -public final class Marker extends Annotation { +public class Marker extends Annotation { private LatLng position; private String snippet; @@ -32,6 +32,13 @@ public final class Marker extends Annotation { super(); } + public Marker(BaseMarkerOptions baseMarkerOptions) { + position = baseMarkerOptions.position; + snippet = baseMarkerOptions.snippet; + icon = baseMarkerOptions.icon; + title = baseMarkerOptions.title; + } + public LatLng getPosition() { return position; } @@ -61,8 +68,17 @@ public final class Marker extends Annotation { return infoWindowShown; } - void setPosition(LatLng position) { + /** + * Sets the position. + * + * @param position new position + */ + public void setPosition(LatLng position) { this.position = position; + MapboxMap map = getMapboxMap(); + if (map != null) { + map.updateMarker(this); + } } void setSnippet(String snippet) { @@ -70,10 +86,16 @@ public final class Marker extends Annotation { } /** - * Do not use this method. Used internally by the SDK. + * Sets the icon. + * + * @param icon The icon to be used as Marker image */ public void setIcon(@Nullable Icon icon) { this.icon = icon; + MapboxMap map = getMapboxMap(); + if (map != null) { + map.updateMarker(this); + } } public Icon getIcon() { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java index a83a6991b2..5cc54cd1ca 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerOptions.java @@ -12,9 +12,9 @@ import com.mapbox.mapboxsdk.geometry.LatLng; * <p> * Builder for composing {@link com.mapbox.mapboxsdk.annotations.Marker} objects. * </p> - * + * <p/> * <h3>Example</h3> - * + * <p/> * <pre> * mMapView.addMarker(new MarkerOptions() * .title("Intersection") @@ -22,7 +22,7 @@ import com.mapbox.mapboxsdk.geometry.LatLng; * .position(new LatLng(38.9002073, -77.03364419))); * </pre> */ -public final class MarkerOptions implements Parcelable { +public final class MarkerOptions extends BaseMarkerOptions<Marker, MarkerOptions> implements Parcelable { public static final Parcelable.Creator<MarkerOptions> CREATOR = new Parcelable.Creator<MarkerOptions>() { @@ -47,6 +47,11 @@ public final class MarkerOptions implements Parcelable { } @Override + public MarkerOptions getThis() { + return this; + } + + @Override public int describeContents() { return 0; } @@ -72,43 +77,27 @@ public final class MarkerOptions implements Parcelable { * @return Marker The build marker */ public Marker getMarker() { + marker.setPosition(position); + marker.setSnippet(snippet); + marker.setTitle(title); + marker.setIcon(icon); return marker; } public LatLng getPosition() { - return marker.getPosition(); + return position; } public String getSnippet() { - return marker.getSnippet(); + return snippet; } public String getTitle() { - return marker.getTitle(); + return title; } public Icon getIcon() { - return marker.getIcon(); - } - - public MarkerOptions position(LatLng position) { - marker.setPosition(position); - return this; - } - - public MarkerOptions snippet(String snippet) { - marker.setSnippet(snippet); - return this; - } - - public MarkerOptions icon(@Nullable Icon icon) { - marker.setIcon(icon); - return this; - } - - public MarkerOptions title(String title) { - marker.setTitle(title); - return this; + return icon; } @Override @@ -125,7 +114,6 @@ public final class MarkerOptions implements Parcelable { if (getIcon() != null ? !getIcon().equals(marker.getIcon()) : marker.getIcon() != null) return false; return !(getTitle() != null ? !getTitle().equals(marker.getTitle()) : marker.getTitle() != null); - } @Override diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java index 004e7f5f84..ec2f0eb316 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java @@ -9,7 +9,6 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.MathConstants; import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.maps.CameraUpdateFactory; import com.mapbox.mapboxsdk.utils.MathUtils; public final class CameraPosition implements Parcelable { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraUpdate.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdate.java index 0f3e710134..c6852624ef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraUpdate.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdate.java @@ -1,4 +1,4 @@ -package com.mapbox.mapboxsdk.maps; +package com.mapbox.mapboxsdk.camera; import android.support.annotation.NonNull; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraUpdateFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java index cad5152316..df156961a0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraUpdateFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java @@ -1,4 +1,4 @@ -package com.mapbox.mapboxsdk.maps; +package com.mapbox.mapboxsdk.camera; import android.graphics.Point; import android.graphics.PointF; @@ -6,15 +6,17 @@ import android.graphics.RectF; import android.support.annotation.IntDef; import android.support.annotation.NonNull; -import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; +import com.mapbox.mapboxsdk.maps.UiSettings; import com.mapbox.mapboxsdk.utils.MathUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -public class CameraUpdateFactory { +public final class CameraUpdateFactory { /** * Returns a CameraUpdate that moves the camera to a specified CameraPosition. @@ -144,7 +146,7 @@ public class CameraUpdateFactory { // CameraUpdate types // - public static class CameraPositionUpdate implements CameraUpdate { + static final class CameraPositionUpdate implements CameraUpdate { private final float bearing; private final LatLng target; @@ -189,7 +191,7 @@ public class CameraUpdateFactory { } } - public static class CameraBoundsUpdate implements CameraUpdate { + static final class CameraBoundsUpdate implements CameraUpdate { private LatLngBounds bounds; private RectF padding; @@ -217,39 +219,40 @@ public class CameraUpdateFactory { @Override public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) { - MapView mapView = mapboxMap.getMapView(); + // Get required objects + Projection projection = mapboxMap.getProjection(); + UiSettings uiSettings = mapboxMap.getUiSettings(); RectF padding = getPadding(); - // Calculate the bounds of the possibly rotated shape with respect to the viewport. + // Calculate the bounds of the possibly rotated shape with respect to the viewport PointF nePixel = new PointF(-10000, -10000); - PointF swPixel = new PointF(1000, 10000); - float viewportHeight = mapView.getHeight(); + PointF swPixel = new PointF(10000, 10000); + float viewportHeight = uiSettings.getHeight(); for (LatLng latLng : getBounds().toLatLngs()) { - PointF pixel = mapView.toScreenLocation(latLng); + PointF pixel = projection.toScreenLocation(latLng); swPixel.x = Math.min(swPixel.x, pixel.x); nePixel.x = Math.max(nePixel.x, pixel.x); swPixel.y = Math.min(swPixel.y, viewportHeight - pixel.y); nePixel.y = Math.max(nePixel.y, viewportHeight - pixel.y); } + // Calculate wid=th/height float width = nePixel.x - swPixel.x; float height = nePixel.y - swPixel.y; - // Calculate the zoom level. - float scaleX = (mapView.getWidth() - padding.left - padding.right) / width; - float scaleY = (mapView.getHeight() - padding.top - padding.bottom) / height; + // Calculate the zoom level + float scaleX = (uiSettings.getWidth() - padding.left - padding.right) / width; + float scaleY = (uiSettings.getHeight() - padding.top - padding.bottom) / height; float minScale = scaleX < scaleY ? scaleX : scaleY; - double zoom = Math.log(mapView.getScale() * minScale) / Math.log(2); - zoom = MathUtils.clamp(zoom, (float) mapView.getMinZoom(), (float) mapView.getMaxZoom()); + double zoom = projection.calculateZoom(minScale); + zoom = MathUtils.clamp(zoom, (float) uiSettings.getMinZoom(), (float) uiSettings.getMaxZoom()); - // Calculate the center point of a virtual bounds that is extended in all directions by padding. + // Calculate the center point PointF paddedNEPixel = new PointF(nePixel.x + padding.right / minScale, nePixel.y + padding.top / minScale); PointF paddedSWPixel = new PointF(swPixel.x - padding.left / minScale, swPixel.y - padding.bottom / minScale); PointF centerPixel = new PointF((paddedNEPixel.x + paddedSWPixel.x) / 2, (paddedNEPixel.y + paddedSWPixel.y) / 2); - centerPixel.y = viewportHeight - centerPixel.y; - - LatLng center = mapboxMap.getProjection().fromScreenLocation(centerPixel); + LatLng center = projection.fromScreenLocation(centerPixel); return new CameraPosition.Builder() .target(center) @@ -260,7 +263,7 @@ public class CameraUpdateFactory { } } - public static class CameraMoveUpdate implements CameraUpdate { + static final class CameraMoveUpdate implements CameraUpdate { private float x; private float y; @@ -272,15 +275,16 @@ public class CameraUpdateFactory { @Override public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) { - MapView mapView = mapboxMap.getMapView(); + UiSettings uiSettings = mapboxMap.getUiSettings(); + Projection projection = mapboxMap.getProjection(); // Calculate the new center point - float viewPortWidth = mapView.getWidth(); - float viewPortHeight = mapView.getHeight(); + float viewPortWidth = uiSettings.getWidth(); + float viewPortHeight = uiSettings.getHeight(); PointF targetPoint = new PointF(viewPortWidth / 2 + x, viewPortHeight / 2 + y); // Convert point to LatLng - LatLng latLng = mapView.fromScreenLocation(targetPoint); + LatLng latLng = projection.fromScreenLocation(targetPoint); CameraPosition cameraPosition = mapboxMap.getCameraPosition(); return new CameraPosition.Builder() @@ -292,7 +296,7 @@ public class CameraUpdateFactory { } } - public static class ZoomUpdate implements CameraUpdate { + static final class ZoomUpdate implements CameraUpdate { @IntDef({ZOOM_IN, ZOOM_OUT, ZOOM_BY, ZOOM_TO, ZOOM_TO_POINT}) @Retention(RetentionPolicy.SOURCE) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java index 33ebfca2fa..f04087818b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java @@ -18,6 +18,16 @@ public class MapboxConstants { public static final String KEY_META_DATA_MANIFEST = "com.mapbox.AccessToken"; /** + * Key used to store staging data server url in AndroidManifest.xml + */ + public static final String KEY_META_DATA_STAGING_SERVER = "com.mapbox.TestEventsServer"; + + /** + * Key used to store staging data server access token in AndroidManifest.xml + */ + public static final String KEY_META_DATA_STAGING_ACCESS_TOKEN = "com.mapbox.TestEventsAccessToken"; + + /** * Default animation time */ public static final int ANIMATION_DURATION = 300; @@ -58,6 +68,7 @@ public class MapboxConstants { public static final String FRAG_ARG_MAPBOXMAPOPTIONS = "MapboxMapOptions"; // Save instance state keys + public static final String STATE_HAS_SAVED_STATE = "savedState"; public static final String STATE_CAMERA_POSITION = "cameraPosition"; public static final String STATE_ZOOM_ENABLED = "zoomEnabled"; public static final String STATE_SCROLL_ENABLED = "scrollEnabled"; @@ -91,4 +102,8 @@ public class MapboxConstants { public static final String STATE_ATTRIBUTION_ENABLED = "atrrEnabled"; public static final String TAG = "MapboxMap"; + + public static final String MAPBOX_SHARED_PREFERENCES_FILE = "MapboxSharedPreferences"; + public static final String MAPBOX_SHARED_PREFERENCE_KEY_VENDORID = "mapboxVendorId"; + public static final String MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED = "mapboxTelemetryEnabled"; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java index 2374a98fc1..383a85417c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java @@ -3,7 +3,7 @@ package com.mapbox.mapboxsdk.constants; import android.support.annotation.IntDef; import com.mapbox.mapboxsdk.maps.MapView; -import com.mapbox.mapboxsdk.maps.UserLocationView; +import com.mapbox.mapboxsdk.maps.widgets.UserLocationView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java index a8008d3742..9b0ae7285e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyLocationTracking.java @@ -3,7 +3,7 @@ package com.mapbox.mapboxsdk.constants; import android.support.annotation.IntDef; import com.mapbox.mapboxsdk.maps.MapView; -import com.mapbox.mapboxsdk.maps.UserLocationView; +import com.mapbox.mapboxsdk.maps.widgets.UserLocationView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TelemetryServiceNotConfiguredException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TelemetryServiceNotConfiguredException.java new file mode 100644 index 0000000000..54908aa58b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/exceptions/TelemetryServiceNotConfiguredException.java @@ -0,0 +1,18 @@ +package com.mapbox.mapboxsdk.exceptions; + +import android.os.Bundle; +import com.mapbox.mapboxsdk.maps.MapView; + +/** + * A {@code TelemetryServiceNotConfiguredException} is thrown by {@link MapView} when it checks and finds that + * TelemetryService has not been configured in the app's AndroidManifest.xml {@link MapView#onCreate(Bundle)} + * + * @see MapView#onCreate(Bundle) + */ +public class TelemetryServiceNotConfiguredException extends RuntimeException { + + public TelemetryServiceNotConfiguredException() { + super("Using Mapbox Android SDK requires configuring TelemetryService. See the INSTALL.md"); + } + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/VisibleRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/VisibleRegion.java index 349120d1f3..de562c3632 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/VisibleRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/VisibleRegion.java @@ -67,6 +67,7 @@ public class VisibleRegion implements Parcelable { * If the other object is actually a pointer to this object, * or if all four corners and the bounds of the two objects are the same, * this method returns true. Otherwise, this method returns false. + * * @param o The Object to compare with. * @return true if both objects are the same object. */ @@ -89,7 +90,15 @@ public class VisibleRegion implements Parcelable { @Override public String toString() { - return "[farLeft [" + farLeft + "], farRight [" + farRight + "], nearLeft [" + nearLeft + "], nearRight [" + nearRight + "], latLngBounds ["+latLngBounds+"]]"; + return "[farLeft [" + farLeft + "], farRight [" + farRight + "], nearLeft [" + nearLeft + "], nearRight [" + nearRight + "], latLngBounds [" + latLngBounds + "]]"; + } + + @Override + public int hashCode() { + return ((farLeft.hashCode() + 90) + + ((farRight.hashCode() + 90) * 1000) + + ((nearLeft.hashCode() + 180) * 1000000) + + ((nearRight.hashCode() + 180) * 1000000000)); } @Override diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java index c385820423..f787085d2f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationListener.java @@ -2,12 +2,15 @@ package com.mapbox.mapboxsdk.location; import android.location.Location; +/** + * Callback interface for when a location change occurs. + */ public interface LocationListener { /** - * Callback method for receiving location updates from LocationServices. + * Callback method for receiving location updates from LocationService. * @param location The new Location data */ - public void onLocationChanged(Location location); + void onLocationChanged(Location location); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationService.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationService.java new file mode 100644 index 0000000000..f459b5ad53 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationService.java @@ -0,0 +1,166 @@ +package com.mapbox.mapboxsdk.location; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationManager; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.util.Log; + +import com.mapbox.mapboxsdk.telemetry.TelemetryLocationReceiver; +import com.mapzen.android.lost.api.LocationRequest; +import com.mapzen.android.lost.api.LostApiClient; + +import java.util.ArrayList; +import java.util.List; + +public class LocationService implements com.mapzen.android.lost.api.LocationListener { + + private static final String TAG = "LocationService"; + + private static LocationService instance; + + private Context context; + private LostApiClient locationClient; + private Location lastLocation; + + private List<LocationListener> locationListeners; + + private boolean isGPSEnabled; + + /** + * Private constructor for singleton LocationService + */ + private LocationService(Context context) { + super(); + this.context = context; + // Setup location services + locationClient = new LostApiClient.Builder(context).build(); + locationListeners = new ArrayList<>(); + } + + /** + * Primary (singleton) access method for LocationService + * + * @param context Context + * @return LocationService + */ + public static LocationService getInstance(@NonNull final Context context) { + if (instance == null) { + instance = new LocationService(context.getApplicationContext()); + } + return instance; + } + + /** + * Enabled / Disable GPS focused location tracking + * + * @param enableGPS true if GPS is to be enabled, false if GPS is to be disabled + */ + public void toggleGPS(boolean enableGPS) { + if ((ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) && + (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) { + Log.w(TAG, "Location Permissions Not Granted Yet. Try again after requesting."); + return; + } + + // Disconnect + if (locationClient.isConnected()) { + // Disconnect first to ensure that the new requests are GPS + com.mapzen.android.lost.api.LocationServices.FusedLocationApi.removeLocationUpdates(this); + locationClient.disconnect(); + } + + // Setup Fresh + locationClient.connect(); + Location lastLocation = com.mapzen.android.lost.api.LocationServices.FusedLocationApi.getLastLocation(); + if (lastLocation != null) { + this.lastLocation = lastLocation; + } + + LocationRequest locationRequest; + + if (enableGPS) { + // LocationRequest Tuned for GPS + locationRequest = LocationRequest.create() + .setFastestInterval(1000) + .setSmallestDisplacement(3.0f) + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + + com.mapzen.android.lost.api.LocationServices.FusedLocationApi.requestLocationUpdates(locationRequest, this); + } else { + // LocationRequest Tuned for PASSIVE + locationRequest = LocationRequest.create() + .setFastestInterval(1000) + .setSmallestDisplacement(3.0f) + .setPriority(LocationRequest.PRIORITY_NO_POWER); + + com.mapzen.android.lost.api.LocationServices.FusedLocationApi.requestLocationUpdates(locationRequest, this); + } + + isGPSEnabled = enableGPS; + } + + /** + * Returns if the GPS sensor is currently enabled + * + * @return active state of the GPS + */ + public boolean isGPSEnabled() { + return isGPSEnabled; + } + + /** + * Called when the location has changed. + * + * @param location The updated location + */ + @Override + public void onLocationChanged(Location location) { + Log.i(TAG, "onLocationChanged()..." + location); + this.lastLocation = location; + + // Update Listeners + for (LocationListener listener : this.locationListeners) { + listener.onLocationChanged(location); + } + + // Update the Telemetry Receiver + Intent locIntent = new Intent(TelemetryLocationReceiver.INTENT_STRING); + locIntent.putExtra(LocationManager.KEY_LOCATION_CHANGED, location); + context.sendBroadcast(locIntent); + } + + /** + * Last known location + * + * @return Last known location data + */ + public Location getLastLocation() { + return lastLocation; + } + + /** + * Registers a LocationListener to receive location updates + * + * @param locationListener LocationListener + */ + public void addLocationListener(@NonNull LocationListener locationListener) { + if (!this.locationListeners.contains(locationListener)) { + this.locationListeners.add(locationListener); + } + } + + /** + * Unregister a LocationListener to stop receiving location updates + * + * @param locationListener LocationListener to remove + * @return True if LocationListener was found and removed, False if it was not + */ + public boolean removeLocationListener(@NonNull LocationListener locationListener) { + return this.locationListeners.remove(locationListener); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java deleted file mode 100644 index ac2ab64076..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationServices.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.mapbox.mapboxsdk.location; - -import android.content.Context; -import android.location.Location; -import android.support.annotation.NonNull; -import com.mapzen.android.lost.api.LocationRequest; -import com.mapzen.android.lost.api.LostApiClient; -import java.util.ArrayList; -import java.util.List; - -public class LocationServices implements com.mapzen.android.lost.api.LocationListener { - - private static LocationServices instance = null; - - private LostApiClient mLocationClient; - private LocationRequest mLocationRequest; - - private Location lastLocation = null; - - private List<LocationListener> locationListeners = null; - - private boolean isGPSEnabled = false; - - /** - * Private constructor for singleton LocationServices - */ - private LocationServices(Context context) { - super(); - // Setup location services - mLocationClient = new LostApiClient.Builder(context).build(); - locationListeners = new ArrayList<>(); - } - - /** - * Primary (singleton) access method for LocationServices - * @param context Context - * @return LocationServices - */ - public static LocationServices getLocationServices(@NonNull final Context context) { - if (instance == null) { - if (context == null) { - throw new NullPointerException("Context required for accessing LocationServices"); - } - instance = new LocationServices(context.getApplicationContext()); - } - return instance; - } - - /** - * Enabled / Disable GPS focused location tracking - * - * @param enableGPS true if GPS is to be enabled, false if GPS is to be disabled - */ - public void toggleGPS(boolean enableGPS) { - - if (enableGPS) { - - if (mLocationClient.isConnected()) { - // Disconnect first to ensure that the new requests are GPS - com.mapzen.android.lost.api.LocationServices.FusedLocationApi.removeLocationUpdates(this); - mLocationClient.disconnect(); - } - - // Setup Fresh - mLocationClient.connect(); - Location lastLocation = com.mapzen.android.lost.api.LocationServices.FusedLocationApi.getLastLocation(); - if (lastLocation != null) { - this.lastLocation = lastLocation; - } - - // LocationRequest Tuned for GPS - mLocationRequest = LocationRequest.create() - .setFastestInterval(1000) - .setSmallestDisplacement(3.0f) - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); - - com.mapzen.android.lost.api.LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, this); - - } else { - - // Disconnect - if (mLocationClient.isConnected()) { - // Disconnect first to ensure that the new requests are GPS - com.mapzen.android.lost.api.LocationServices.FusedLocationApi.removeLocationUpdates(this); - mLocationClient.disconnect(); - } - - } - - isGPSEnabled = enableGPS; - } - - public boolean isGPSEnabled() { - return isGPSEnabled; - } - - @Override - public void onLocationChanged(Location location) { - this.lastLocation = location; - - // Update Listeners - for (LocationListener listener : this.locationListeners) { - listener.onLocationChanged(location); - } - } - - /** - * Last known location - * @return Last known location data - */ - public Location getLastLocation() { - return lastLocation; - } - - /** - * Registers a LocationListener to receive location updates - * @param locationListener LocationListener - */ - public void addLocationListener(@NonNull LocationListener locationListener) { - if(!this.locationListeners.contains(locationListener)){ - this.locationListeners.add(locationListener); - } - } - - /** - * Unregister a LocationListener to stop receiving location updates - * @param locationListener LocationListener to remove - * @return True if LocationListener was found and removed, False if it was not - */ - public boolean removeLocationListener(@NonNull LocationListener locationListener) { - return this.locationListeners.remove(locationListener); - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java index 2789e85ed8..4b5aa0cbaf 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java @@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.maps; import android.app.Fragment; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; @@ -29,18 +30,10 @@ public class MapFragment extends Fragment { private MapView mMap; - public static MapFragment newInstance(){ + public static MapFragment newInstance() { return new MapFragment(); } - public static MapFragment newInstance(MapboxMapOptions mapboxMapOptions) { - final MapFragment mapFragment = new MapFragment(); - Bundle bundle = new Bundle(); - bundle.putParcelable(MapboxConstants.FRAG_ARG_MAPBOXMAPOPTIONS, mapboxMapOptions); - mapFragment.setArguments(bundle); - return mapFragment; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); @@ -94,7 +87,12 @@ public class MapFragment extends Fragment { } @NonNull - public void getMapAsync(@NonNull OnMapReadyCallback onMapReadyCallback){ - mMap.getMapAsync(onMapReadyCallback); + public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) { + new Handler().post(new Runnable() { + @Override + public void run() { + mMap.getMapAsync(onMapReadyCallback); + } + }); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index bd0a010900..cb9459d5f2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -10,7 +10,9 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -27,8 +29,8 @@ import android.support.annotation.FloatRange; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.support.v7.app.AlertDialog; @@ -47,44 +49,50 @@ import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.ListView; import android.widget.ZoomButtonsController; - import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; import com.almeros.android.multitouch.gesturedetectors.ShoveGestureDetector; import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.Icon; +import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.annotations.Polygon; -import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; -import com.mapbox.mapboxsdk.annotations.PolylineOptions; -import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException; import com.mapbox.mapboxsdk.exceptions.InvalidAccessTokenException; +import com.mapbox.mapboxsdk.exceptions.TelemetryServiceNotConfiguredException; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.layers.CustomLayer; +import com.mapbox.mapboxsdk.maps.widgets.CompassView; +import com.mapbox.mapboxsdk.maps.widgets.UserLocationView; +import com.mapbox.mapboxsdk.telemetry.MapboxEvent; +import com.mapbox.mapboxsdk.telemetry.MapboxEventManager; import com.mapbox.mapboxsdk.utils.ApiAccess; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Hashtable; +import java.util.Iterator; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * <p> @@ -111,7 +119,6 @@ public class MapView extends FrameLayout { private static final float DIMENSION_SEVENTYSIX_DP = 76f; private MapboxMap mMapboxMap; - private List<Annotation> mAnnotations; private List<Icon> mIcons; private NativeMapView mNativeMapView; @@ -120,7 +127,7 @@ public class MapView extends FrameLayout { private ImageView mAttributionsView; private UserLocationView mUserLocationView; - private List<OnMapChangedListener> mOnMapChangedListener; + private CopyOnWriteArrayList<OnMapChangedListener> mOnMapChangedListener; private ZoomButtonsController mZoomButtonsController; private ConnectivityReceiver mConnectivityReceiver; private float mScreenDensity = 1.0f; @@ -133,13 +140,12 @@ public class MapView extends FrameLayout { private boolean mTwoTap = false; private boolean mZoomStarted = false; private boolean mQuickZoom = false; + private boolean mScrollInProgress = false; - /* private int mContentPaddingLeft; private int mContentPaddingTop; private int mContentPaddingRight; private int mContentPaddingBottom; -*/ @UiThread public MapView(@NonNull Context context) { @@ -160,11 +166,9 @@ public class MapView extends FrameLayout { } private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) { - mOnMapChangedListener = new ArrayList<>(); + mOnMapChangedListener = new CopyOnWriteArrayList<>(); mMapboxMap = new MapboxMap(this); - mAnnotations = new ArrayList<>(); mIcons = new ArrayList<>(); - View view = LayoutInflater.from(context).inflate(R.layout.mapview_internal, this); if (!isInEditMode()) { @@ -200,7 +204,7 @@ public class MapView extends FrameLayout { // Shows the zoom controls if (!context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { - mMapboxMap.setZoomControlsEnabled(true); + mMapboxMap.getUiSettings().setZoomControlsEnabled(true); } mZoomButtonsController = new ZoomButtonsController(this); mZoomButtonsController.setZoomSpeed(MapboxConstants.ANIMATION_DURATION); @@ -210,9 +214,9 @@ public class MapView extends FrameLayout { onConnectivityChanged(isConnected()); mUserLocationView = (UserLocationView) view.findViewById(R.id.userLocationView); - mUserLocationView.setMapView(this); + mUserLocationView.setMapboxMap(mMapboxMap); mCompassView = (CompassView) view.findViewById(R.id.compassView); - mCompassView.setOnClickListener(new CompassView.CompassClickListener(this)); + mCompassView.setOnClickListener(new CompassView.CompassClickListener(mMapboxMap)); mLogoView = (ImageView) view.findViewById(R.id.logoView); // Setup Attributions control @@ -232,13 +236,15 @@ public class MapView extends FrameLayout { mMapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); // Access token - if (typedArray.getString(R.styleable.MapView_access_token) != null) { + String accessToken = typedArray.getString(R.styleable.MapView_access_token); + if (accessToken != null) { setAccessToken(typedArray.getString(R.styleable.MapView_access_token)); } // Style url - if (typedArray.getString(R.styleable.MapView_style_url) != null) { - mMapboxMap.setStyleUrl(typedArray.getString(R.styleable.MapView_style_url)); + String styleUrl = typedArray.getString(R.styleable.MapView_style_url); + if (styleUrl != null) { + mMapboxMap.setStyleUrl(styleUrl); } // Enable gestures @@ -249,6 +255,10 @@ public class MapView extends FrameLayout { uiSettings.setTiltGesturesEnabled(typedArray.getBoolean(R.styleable.MapView_tilt_enabled, true)); uiSettings.setZoomControlsEnabled(typedArray.getBoolean(R.styleable.MapView_zoom_controls_enabled, false)); + // Zoom + uiSettings.setMaxZoom(typedArray.getFloat(R.styleable.MapView_zoom_max, (float) MapboxConstants.MAXIMUM_ZOOM)); + uiSettings.setMinZoom(typedArray.getFloat(R.styleable.MapView_zoom_min, (float) MapboxConstants.MINIMUM_ZOOM)); + // Compass uiSettings.setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compass_enabled, true)); uiSettings.setCompassGravity(typedArray.getInt(R.styleable.MapView_compass_gravity, Gravity.TOP | Gravity.END)); @@ -274,13 +284,7 @@ public class MapView extends FrameLayout { , (int) (typedArray.getDimension(R.styleable.MapView_attribution_margin_bottom, DIMENSION_SEVEN_DP) * mScreenDensity)); // User location - try { - //noinspection ResourceType - mMapboxMap.setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false)); - } catch (SecurityException ignore) { - // User did not accept location permissions - } - + mMapboxMap.setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_my_location_enabled, false)); } finally { typedArray.recycle(); } @@ -303,7 +307,7 @@ public class MapView extends FrameLayout { */ @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { - if (savedInstanceState != null) { + if (savedInstanceState != null && savedInstanceState.getBoolean(MapboxConstants.STATE_HAS_SAVED_STATE)) { // Get previous camera position CameraPosition cameraPosition = savedInstanceState.getParcelable(MapboxConstants.STATE_CAMERA_POSITION); @@ -357,10 +361,17 @@ public class MapView extends FrameLayout { // User did not accept location permissions } + TrackingSettings trackingSettings = mMapboxMap.getTrackingSettings(); //noinspection ResourceType - mMapboxMap.setMyLocationTrackingMode(savedInstanceState.getInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE)); + trackingSettings.setMyLocationTrackingMode(savedInstanceState.getInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, MyLocationTracking.TRACKING_NONE)); //noinspection ResourceType - mMapboxMap.setMyBearingTrackingMode(savedInstanceState.getInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, MyBearingTracking.NONE)); + trackingSettings.setMyBearingTrackingMode(savedInstanceState.getInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, MyBearingTracking.NONE)); + } else { + // Force a check for Telemetry + validateTelemetryServiceConfigured(); + + // Start Telemetry (authorization determined in initial MapboxEventManager constructor) + MapboxEventManager.getMapboxEventManager(getContext()).isTelemetryEnabled(); } // Force a check for an access token @@ -381,6 +392,16 @@ public class MapView extends FrameLayout { } } }); + + // Fire MapLoad + if (savedInstanceState == null) { + Hashtable<String, Object> evt = new Hashtable<>(); + evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_LOAD); + evt.put(MapboxEvent.KEY_LATITUDE, mMapboxMap.getCameraPosition().target.getLatitude()); + evt.put(MapboxEvent.KEY_LONGITUDE, mMapboxMap.getCameraPosition().target.getLongitude()); + evt.put(MapboxEvent.KEY_ZOOM, mMapboxMap.getCameraPosition().zoom); + MapboxEventManager.getMapboxEventManager(getContext()).pushEvent(evt); + } } /** @@ -392,14 +413,19 @@ public class MapView extends FrameLayout { @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); outState.putParcelable(MapboxConstants.STATE_CAMERA_POSITION, mMapboxMap.getCameraPosition()); outState.putBoolean(MapboxConstants.STATE_DEBUG_ACTIVE, mMapboxMap.isDebugActive()); outState.putString(MapboxConstants.STATE_STYLE_URL, mMapboxMap.getStyleUrl()); outState.putString(MapboxConstants.STATE_ACCESS_TOKEN, mMapboxMap.getAccessToken()); outState.putLong(MapboxConstants.STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration()); outState.putBoolean(MapboxConstants.STATE_MY_LOCATION_ENABLED, mMapboxMap.isMyLocationEnabled()); - outState.putInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, mMapboxMap.getMyLocationTrackingMode()); - outState.putInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, mMapboxMap.getMyBearingTrackingMode()); + + + // TrackingSettings + TrackingSettings trackingSettings = mMapboxMap.getTrackingSettings(); + outState.putInt(MapboxConstants.STATE_MY_LOCATION_TRACKING_MODE, trackingSettings.getMyLocationTrackingMode()); + outState.putInt(MapboxConstants.STATE_MY_BEARING_TRACKING_MODE, trackingSettings.getMyBearingTrackingMode()); // UiSettings UiSettings uiSettings = mMapboxMap.getUiSettings(); @@ -554,13 +580,13 @@ public class MapView extends FrameLayout { } // - // Rotation + // Direction // /** - * Returns the current heading of the map relative to true north. + * Returns the current direction of the map relative to true north. * - * @return The current heading measured in degrees. + * @return The current direction measured in degrees. */ @UiThread @FloatRange(from = MapboxConstants.MINIMUM_DIRECTION, to = MapboxConstants.MAXIMUM_DIRECTION) @@ -633,6 +659,46 @@ public class MapView extends FrameLayout { } // + // Content padding + // + + /** + * Return The current content padding left of the map view viewport. + * + * @return The current content padding left + */ + int getContentPaddingLeft() { + return mContentPaddingLeft; + } + + /** + * Return The current content padding left of the map view viewport. + * + * @return The current content padding left + */ + int getContentPaddingTop() { + return mContentPaddingTop; + } + + /** + * Return The current content padding left of the map view viewport. + * + * @return The current content padding right + */ + int getContentPaddingRight() { + return mContentPaddingRight; + } + + /** + * Return The current content padding left of the map view viewport. + * + * @return The current content padding bottom + */ + int getContentPaddingBottom() { + return mContentPaddingBottom; + } + + // // Zoom // @@ -647,51 +713,6 @@ public class MapView extends FrameLayout { return mNativeMapView.getZoom(); } -// /** -// * Return The current content padding left of the map view viewport. -// * -// * @return The current content padding left -// */ -///* -// public int getContentPaddingLeft() { -// return mContentPaddingLeft; -// } -//*/ -// -// /** -// * Return The current content padding left of the map view viewport. -// * -// * @return The current content padding left -// */ -///* -// public int getContentPaddingTop() { -// return mContentPaddingTop; -// } -//*/ -// -// /** -// * Return The current content padding left of the map view viewport. -// * -// * @return The current content padding left -// */ -///* -// public int getContentPaddingRight() { -// return mContentPaddingRight; -// } -//*/ -// -// /** -// * Return The current content padding left of the map view viewport. -// * -// * @param zoomLevel The new zoom level. -// * @param animated If true, animates the change. If false, immediately changes the map. -// * @see MapboxMap#MAXIMUM_ZOOM -// */ -///* -// public int getContentPaddingBottom() { -// return mContentPaddingBottom; -//*/ - /** * <p> * Sets the minimum zoom level the map can be displayed at. @@ -712,7 +733,7 @@ public class MapView extends FrameLayout { * @return The minimum zoom level. */ @UiThread - public double getMinZoom() { + double getMinZoom() { return mNativeMapView.getMinZoom(); } @@ -724,7 +745,7 @@ public class MapView extends FrameLayout { * @param maxZoom The new maximum zoom level. */ @UiThread - public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { + void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { mNativeMapView.setMaxZoom(maxZoom); } @@ -736,7 +757,7 @@ public class MapView extends FrameLayout { * @return The maximum zoom level. */ @UiThread - public double getMaxZoom() { + double getMaxZoom() { return mNativeMapView.getMaxZoom(); } @@ -866,6 +887,28 @@ public class MapView extends FrameLayout { } } + // Checks that TelemetryService has been configured by developer + private void validateTelemetryServiceConfigured() { + + try { + // Check Implementing app's AndroidManifest.xml + PackageInfo packageInfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), PackageManager.GET_SERVICES); + + if (packageInfo.services != null) { + + for (ServiceInfo service : packageInfo.services) { + if (TextUtils.equals("com.mapbox.mapboxsdk.telemetry.TelemetryService", service.name)) { + return; + } + } + } + + } catch (Exception e) { + Log.w(TAG, "Error checking for Telemetry Service Config: " + e); + } + throw new TelemetryServiceNotConfiguredException(); + } + /** * <p> * Sets the current Mapbox access token used to load map styles and tiles. @@ -942,7 +985,25 @@ public class MapView extends FrameLayout { // Annotations // - private void loadIcon(Icon icon) { + Icon loadIconForMarker(Marker marker) { + Icon icon = marker.getIcon(); + if (icon == null) { + icon = IconFactory.getInstance(getContext()).defaultMarker(); + marker.setIcon(icon); + } + if (!mIcons.contains(icon)) { + mIcons.add(icon); + loadIcon(icon); + } else { + Icon oldIcon = mIcons.get(mIcons.indexOf(icon)); + if (!oldIcon.getBitmap().sameAs(icon.getBitmap())) { + throw new IconBitmapChangedException(); + } + } + return icon; + } + + void loadIcon(Icon icon) { Bitmap bitmap = icon.getBitmap(); String id = icon.getId(); if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { @@ -964,7 +1025,7 @@ public class MapView extends FrameLayout { scale, buffer.array()); } - private void reloadIcons() { + void reloadIcons() { int count = mIcons.size(); for (int i = 0; i < count; i++) { Icon icon = mIcons.get(i); @@ -972,8 +1033,35 @@ public class MapView extends FrameLayout { } } + /** + * <p> + * Updates a marker on this map. Does nothing if the marker is already added. + * </p> + * + * @param updatedMarker An updated marker object. + */ + @UiThread + void updateMarker(@NonNull Marker updatedMarker) { + if (updatedMarker == null) { + Log.w(TAG, "marker was null, doing nothing"); + return; + } + + if (updatedMarker.getId() == -1) { + Log.w(TAG, "marker has an id of -1, possibly was not added yet, doing nothing"); + } + + ensureIconLoaded(updatedMarker); + mNativeMapView.updateMarker(updatedMarker); + } + private Marker prepareMarker(MarkerOptions markerOptions) { Marker marker = markerOptions.getMarker(); + ensureIconLoaded(marker); + return marker; + } + + private void ensureIconLoaded(Marker marker) { Icon icon = marker.getIcon(); if (icon == null) { icon = IconFactory.getInstance(getContext()).defaultMarker(); @@ -988,270 +1076,48 @@ public class MapView extends FrameLayout { throw new IconBitmapChangedException(); } } - marker.setTopOffsetPixels(getTopOffsetPixelsForIcon(icon)); - return marker; - } - /** - * <p> - * Adds a marker to this map. - * </p> - * The marker's icon is rendered on the map at the location {@code Marker.position}. - * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet. - * - * @param markerOptions A marker options object that defines how to render the marker. - * @return The {@code Marker} that was added to the map. - */ - @UiThread - @NonNull - Marker addMarker(@NonNull MarkerOptions markerOptions) { - if (markerOptions == null) { - Log.w(TAG, "markerOptions was null, so just returning null"); - return null; + // this seems to be a costly operation according to the profiler so I'm trying to save some calls + Marker previousMarker = marker.getId() != -1 ? (Marker) mMapboxMap.getAnnotation(marker.getId()) : null; + if (previousMarker == null || previousMarker.getIcon() == null || previousMarker.getIcon() != marker.getIcon()) { + marker.setTopOffsetPixels(getTopOffsetPixelsForIcon(icon)); } - - Marker marker = prepareMarker(markerOptions); - long id = mNativeMapView.addMarker(marker); - marker.setId(id); // the annotation needs to know its id - marker.setMapboxMap(mMapboxMap); // the annotation needs to know which map view it is in - mAnnotations.add(marker); - return marker; } - /** - * <p> - * Adds multiple markers to this map. - * </p> - * The marker's icon is rendered on the map at the location {@code Marker.position}. - * If {@code Marker.title} is defined, the map shows an info box with the marker's title and snippet. - * - * @param markerOptionsList A list of marker options objects that defines how to render the markers. - * @return A list of the {@code Marker}s that were added to the map. - */ - @UiThread - @NonNull - List<Marker> addMarkers(@NonNull List<MarkerOptions> markerOptionsList) { - if (markerOptionsList == null) { - Log.w(TAG, "markerOptionsList was null, so just returning null"); - return null; - } - - int count = markerOptionsList.size(); - List<Marker> markers = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - MarkerOptions markerOptions = markerOptionsList.get(i); - Marker marker = prepareMarker(markerOptions); - markers.add(marker); - } - - long[] ids = mNativeMapView.addMarkers(markers); - Marker m; - for (int i = 0; i < count; i++) { - m = markers.get(i); - m.setId(ids[i]); - m.setMapboxMap(mMapboxMap); - mAnnotations.add(m); + long addMarker(@NonNull Marker marker) { + if (mNativeMapView == null) { + return 0l; } - - return new ArrayList<>(markers); + return mNativeMapView.addMarker(marker); } - /** - * Adds a polyline to this map. - * - * @param polylineOptions A polyline options object that defines how to render the polyline. - * @return The {@code Polyine} that was added to the map. - */ - @UiThread - @NonNull - Polyline addPolyline(@NonNull PolylineOptions polylineOptions) { - if (polylineOptions == null) { - Log.w(TAG, "polylineOptions was null, so just returning null"); - return null; - } - - Polyline polyline = polylineOptions.getPolyline(); - long id = mNativeMapView.addPolyline(polyline); - polyline.setId(id); - polyline.setMapboxMap(mMapboxMap); - mAnnotations.add(polyline); - return polyline; + long[] addMarkers(@NonNull List<Marker> markerList) { + return mNativeMapView.addMarkers(markerList); } - /** - * Adds multiple polylines to this map. - * - * @param polylineOptionsList A list of polyline options objects that defines how to render the polylines. - * @return A list of the {@code Polyline}s that were added to the map. - */ - @UiThread - @NonNull - List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList) { - if (polylineOptionsList == null) { - Log.w(TAG, "polylineOptionsList was null, so just returning null"); - return null; - } - - int count = polylineOptionsList.size(); - List<Polyline> polylines = new ArrayList<>(count); - for (PolylineOptions options : polylineOptionsList) { - polylines.add(options.getPolyline()); - } - - long[] ids = mNativeMapView.addPolylines(polylines); - - Polyline p; - for (int i = 0; i < count; i++) { - p = polylines.get(i); - p.setId(ids[i]); - p.setMapboxMap(mMapboxMap); - mAnnotations.add(p); - } - - return new ArrayList<>(polylines); + long addPolyline(@NonNull Polyline polyline) { + return mNativeMapView.addPolyline(polyline); } - /** - * Adds a polygon to this map. - * - * @param polygonOptions A polygon options object that defines how to render the polygon. - * @return The {@code Polygon} that was added to the map. - */ - @UiThread - @NonNull - Polygon addPolygon(@NonNull PolygonOptions polygonOptions) { - if (polygonOptions == null) { - Log.w(TAG, "polygonOptions was null, so just returning null"); - return null; - } - - Polygon polygon = polygonOptions.getPolygon(); - long id = mNativeMapView.addPolygon(polygon); - polygon.setId(id); - polygon.setMapboxMap(mMapboxMap); - mAnnotations.add(polygon); - return polygon; + long[] addPolylines(@NonNull List<Polyline> polylines) { + return mNativeMapView.addPolylines(polylines); } - - /** - * Adds multiple polygons to this map. - * - * @param polygonOptionsList A list of polygon options objects that defines how to render the polygons. - * @return A list of the {@code Polygon}s that were added to the map. - */ - @UiThread - @NonNull - List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList) { - if (polygonOptionsList == null) { - Log.w(TAG, "polygonOptionsList was null, so just returning null"); - return null; - } - - int count = polygonOptionsList.size(); - List<Polygon> polygons = new ArrayList<>(count); - for (PolygonOptions polygonOptions : polygonOptionsList) { - polygons.add(polygonOptions.getPolygon()); - } - - long[] ids = mNativeMapView.addPolygons(polygons); - - Polygon p; - for (int i = 0; i < count; i++) { - p = polygons.get(i); - p.setId(ids[i]); - p.setMapboxMap(mMapboxMap); - mAnnotations.add(p); - } - - return new ArrayList<>(polygons); + long addPolygon(@NonNull Polygon polygon) { + return mNativeMapView.addPolygon(polygon); } - - /** - * <p> - * Convenience method for removing a Marker from the map. - * </p> - * Calls removeAnnotation() internally - * - * @param marker Marker to remove - */ - @UiThread - void removeMarker(@NonNull Marker marker) { - removeAnnotation(marker); + long[] addPolygons(@NonNull List<Polygon> polygons) { + return mNativeMapView.addPolygons(polygons); } - /** - * Removes an annotation from the map. - * - * @param annotation The annotation object to remove. - */ - @UiThread - void removeAnnotation(@NonNull Annotation annotation) { - if (annotation == null) { - Log.w(TAG, "annotation was null, so just returning"); - return; - } - - if (annotation instanceof Marker) { - ((Marker) annotation).hideInfoWindow(); - } - long id = annotation.getId(); + void removeAnnotation(long id) { mNativeMapView.removeAnnotation(id); - mAnnotations.remove(annotation); - } - - /** - * Removes multiple annotations from the map. - * - * @param annotationList A list of annotation objects to remove. - */ - @UiThread - void removeAnnotations(@NonNull List<? extends Annotation> annotationList) { - if (annotationList == null) { - Log.w(TAG, "annotationList was null, so just returning"); - return; - } - - int count = annotationList.size(); - long[] ids = new long[count]; - for (int i = 0; i < count; i++) { - ids[i] = annotationList.get(i).getId(); - } - mNativeMapView.removeAnnotations(ids); } - /** - * Removes all annotations from the map. - */ - @UiThread - void removeAllAnnotations() { - int count = mAnnotations.size(); - long[] ids = new long[mAnnotations.size()]; - - for (int i = 0; i < count; i++) { - Annotation annotation = mAnnotations.get(i); - long id = annotation.getId(); - ids[i] = id; - if (annotation instanceof Marker) { - ((Marker) annotation).hideInfoWindow(); - } - } - + void removeAnnotations(@NonNull long[] ids) { mNativeMapView.removeAnnotations(ids); - mAnnotations.clear(); - } - - /** - * Returns a list of all the annotations on the map. - * - * @return A list of all the annotation objects. The returned object is a copy so modifying this - * list will not update the map. - */ - @NonNull - List<Annotation> getAllAnnotations() { - return new ArrayList<>(mAnnotations); } private List<Marker> getMarkersInBounds(@NonNull LatLngBounds bbox) { @@ -1269,9 +1135,10 @@ public class MapView extends FrameLayout { } List<Marker> annotations = new ArrayList<>(ids.length); - int count = mAnnotations.size(); + List<Annotation> annotationList = mMapboxMap.getAnnotations(); + int count = annotationList.size(); for (int i = 0; i < count; i++) { - Annotation annotation = mAnnotations.get(i); + Annotation annotation = annotationList.get(i); if (annotation instanceof Marker && idsList.contains(annotation.getId())) { annotations.add((Marker) annotation); } @@ -1280,7 +1147,7 @@ public class MapView extends FrameLayout { return new ArrayList<>(annotations); } - private int getTopOffsetPixelsForIcon(Icon icon) { + int getTopOffsetPixelsForIcon(Icon icon) { // This method will dead lock if map paused. Causes a freeze if you add a marker in an // activity's onCreate() if (mNativeMapView.isPaused()) { @@ -1292,6 +1159,35 @@ public class MapView extends FrameLayout { } /** + * Sets the distance from the edges of the map view’s frame to the edges of the map + * view’s logical viewport. + * <p/> + * When the value of this property is equal to {0,0,0,0}, viewport + * properties such as `centerCoordinate` assume a viewport that matches the map + * view’s frame. Otherwise, those properties are inset, excluding part of the + * frame from the viewport. For instance, if the only the top edge is inset, the + * map center is effectively shifted downward. + * + * @param left The left margin in pixels. + * @param top The top margin in pixels. + * @param right The right margin in pixels. + * @param bottom The bottom margin in pixels. + */ + @UiThread + void setContentPadding(int left, int top, int right, int bottom) { + if (left == mContentPaddingLeft && top == mContentPaddingTop && right == mContentPaddingRight && bottom == mContentPaddingBottom) { + return; + } + + mContentPaddingLeft = left; + mContentPaddingTop = top; + mContentPaddingRight = right; + mContentPaddingBottom = bottom; + + mNativeMapView.setContentPadding(top / mScreenDensity, left / mScreenDensity, bottom / mScreenDensity, right / mScreenDensity); + } + + /** * <p> * Returns the distance spanned by one pixel at the specified latitude and current zoom level. * </p> @@ -1400,9 +1296,10 @@ public class MapView extends FrameLayout { } private void adjustTopOffsetPixels() { - int count = mAnnotations.size(); + List<Annotation> annotations = mMapboxMap.getAnnotations(); + int count = annotations.size(); for (int i = 0; i < count; i++) { - Annotation annotation = mAnnotations.get(i); + Annotation annotation = annotations.get(i); if (annotation instanceof Marker) { Marker marker = (Marker) annotation; marker.setTopOffsetPixels( @@ -1419,9 +1316,10 @@ public class MapView extends FrameLayout { } private void reloadMarkers() { - int count = mAnnotations.size(); + List<Annotation> annotations = mMapboxMap.getAnnotations(); + int count = annotations.size(); for (int i = 0; i < count; i++) { - Annotation annotation = mAnnotations.get(i); + Annotation annotation = annotations.get(i); if (annotation instanceof Marker) { Marker marker = (Marker) annotation; mNativeMapView.removeAnnotation(annotation.getId()); @@ -1572,6 +1470,26 @@ public class MapView extends FrameLayout { // Touch events // + /** + * Helper method for tracking gesture events + * @param gestureId Type of Gesture See {@see MapboxEvent#GESTURE_SINGLETAP MapboxEvent#GESTURE_DOUBLETAP MapboxEvent#GESTURE_TWO_FINGER_SINGLETAP MapboxEvent#GESTURE_QUICK_ZOOM MapboxEvent#GESTURE_PAN_START MapboxEvent#GESTURE_PINCH_START MapboxEvent#GESTURE_ROTATION_START MapboxEvent#GESTURE_PITCH_START} + * @param xCoordinate Original x screen coordinate at start of gesture + * @param yCoordinate Original y screen cooridnate at start of gesture + */ + private void trackGestureEvent(@NonNull String gestureId, @NonNull float xCoordinate, float yCoordinate) { + + LatLng tapLatLng = fromScreenLocation(new PointF(xCoordinate, yCoordinate)); + + Hashtable<String, Object> evt = new Hashtable<>(); + evt.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_MAP_CLICK); + evt.put(MapboxEvent.KEY_GESTURE_ID, gestureId); + evt.put(MapboxEvent.KEY_LATITUDE, tapLatLng.getLatitude()); + evt.put(MapboxEvent.KEY_LONGITUDE, tapLatLng.getLongitude()); + evt.put(MapboxEvent.KEY_ZOOM, mMapboxMap.getCameraPosition().zoom); + + MapboxEventManager.getMapboxEventManager(getContext()).pushEvent(evt); + } + // Called when user touches the screen, all positions are absolute @Override public boolean onTouchEvent(@NonNull MotionEvent event) { @@ -1596,6 +1514,10 @@ public class MapView extends FrameLayout { case MotionEvent.ACTION_POINTER_DOWN: // Second pointer down mTwoTap = event.getPointerCount() == 2; + if (mTwoTap) { + // Confirmed 2nd Finger Down + trackGestureEvent(MapboxEvent.GESTURE_TWO_FINGER_SINGLETAP, event.getX(), event.getY()); + } break; case MotionEvent.ACTION_POINTER_UP: @@ -1617,6 +1539,12 @@ public class MapView extends FrameLayout { return true; } + // Scroll / Pan Has Stopped + if (mScrollInProgress) { + trackGestureEvent(MapboxEvent.TYPE_MAP_DRAGEND, event.getX(), event.getY()); + mScrollInProgress = false; + } + mTwoTap = false; mNativeMapView.setGestureInProgress(false); break; @@ -1632,8 +1560,7 @@ public class MapView extends FrameLayout { } // This class handles one finger gestures - private class GestureListener extends - GestureDetector.SimpleOnGestureListener { + private class GestureListener extends GestureDetector.SimpleOnGestureListener { // Must always return true otherwise all events are ignored @Override @@ -1665,16 +1592,20 @@ public class MapView extends FrameLayout { } // Single finger double tap - if (mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) { + if (mMapboxMap.getTrackingSettings().isLocationTrackingDisabled()) { // Zoom in on gesture zoom(true, e.getX(), e.getY()); + trackGestureEvent(MapboxEvent.GESTURE_QUICK_ZOOM, e.getX(), e.getY()); } else { - // Zoom in on center map - zoom(true, getWidth() / 2, getHeight() / 2); + // Zoom in on user location view + PointF centerPoint = mUserLocationView.getMarkerScreenPoint(); + zoom(true, centerPoint.x, centerPoint.y); } break; } + trackGestureEvent(MapboxEvent.GESTURE_DOUBLETAP, e.getX(), e.getY()); + return true; } @@ -1725,9 +1656,10 @@ public class MapView extends FrameLayout { } if (newSelectedMarkerId >= 0) { - int count = mAnnotations.size(); + List<Annotation> annotations = mMapboxMap.getAnnotations(); + int count = annotations.size(); for (int i = 0; i < count; i++) { - Annotation annotation = mAnnotations.get(i); + Annotation annotation = annotations.get(i); if (annotation instanceof Marker) { if (annotation.getId() == newSelectedMarkerId) { if (selectedMarkers.isEmpty() || !selectedMarkers.contains(annotation)) { @@ -1749,6 +1681,8 @@ public class MapView extends FrameLayout { } } + trackGestureEvent(MapboxEvent.GESTURE_SINGLETAP, e.getX(), e.getY()); + return true; } @@ -1771,7 +1705,9 @@ public class MapView extends FrameLayout { } // reset tracking modes if gesture occurs - resetTrackingModes(); + if (mMapboxMap.getTrackingSettings().isDismissTrackingOnGesture()) { + resetTrackingModes(); + } // Fling the map float ease = 0.25f; @@ -1793,18 +1729,25 @@ public class MapView extends FrameLayout { listener.onFling(); } + trackGestureEvent(MapboxEvent.GESTURE_PAN_START, e1.getX(), e1.getY()); return true; } // Called for drags @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + Log.i(TAG, "onScroll() started"); + if (!mScrollInProgress) { + mScrollInProgress = true; + } if (!mMapboxMap.getUiSettings().isScrollGesturesEnabled()) { return false; } - // reset tracking modes if gesture occurs - resetTrackingModes(); + if (mMapboxMap.getTrackingSettings().isDismissTrackingOnGesture()) { + // reset tracking modes if gesture occurs + resetTrackingModes(); + } // Cancel any animation mNativeMapView.cancelTransitions(); @@ -1817,6 +1760,7 @@ public class MapView extends FrameLayout { listener.onScroll(); } + Log.i(TAG, "onScroll() done"); return true; } } @@ -1834,10 +1778,13 @@ public class MapView extends FrameLayout { return false; } - // reset tracking modes if gesture occurs - resetTrackingModes(); + if (mMapboxMap.getTrackingSettings().isDismissTrackingOnGesture()) { + // reset tracking modes if gesture occurs + resetTrackingModes(); + } mBeginTime = detector.getEventTime(); + trackGestureEvent(MapboxEvent.GESTURE_PINCH_START, detector.getFocusX(), detector.getFocusY()); return true; } @@ -1853,7 +1800,8 @@ public class MapView extends FrameLayout { // Called for pinch zooms and quickzooms/quickscales @Override public boolean onScale(ScaleGestureDetector detector) { - if (!mMapboxMap.getUiSettings().isZoomGesturesEnabled()) { + UiSettings uiSettings = mMapboxMap.getUiSettings(); + if (!uiSettings.isZoomGesturesEnabled()) { return false; } @@ -1881,13 +1829,21 @@ public class MapView extends FrameLayout { // Gesture is a quickzoom if there aren't two fingers mQuickZoom = !mTwoTap; + TrackingSettings trackingSettings = mMapboxMap.getTrackingSettings(); + // Scale the map - if (mMapboxMap.getUiSettings().isScrollGesturesEnabled() && !mQuickZoom && mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) { + if (uiSettings.isScrollGesturesEnabled() && !mQuickZoom && trackingSettings.isLocationTrackingDisabled()) { // around gesture mNativeMapView.scaleBy(detector.getScaleFactor(), detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity); } else { - // around center map - mNativeMapView.scaleBy(detector.getScaleFactor(), (getWidth() / 2) / mScreenDensity, (getHeight() / 2) / mScreenDensity); + if(trackingSettings.isLocationTrackingDisabled()) { + // around center map + mNativeMapView.scaleBy(detector.getScaleFactor(), (getWidth() / 2) / mScreenDensity, (getHeight() / 2) / mScreenDensity); + }else { + // around user location view + PointF centerPoint = mUserLocationView.getMarkerScreenPoint(); + mNativeMapView.scaleBy(detector.getScaleFactor(), centerPoint.x / mScreenDensity, centerPoint.y / mScreenDensity); + } } return true; } @@ -1907,10 +1863,13 @@ public class MapView extends FrameLayout { return false; } - // reset tracking modes if gesture occurs - resetTrackingModes(); + if (mMapboxMap.getTrackingSettings().isDismissTrackingOnGesture()) { + // reset tracking modes if gesture occurs + resetTrackingModes(); + } mBeginTime = detector.getEventTime(); + trackGestureEvent(MapboxEvent.GESTURE_ROTATION_START, detector.getFocusX(), detector.getFocusY()); return true; } @@ -1957,16 +1916,15 @@ public class MapView extends FrameLayout { bearing += detector.getRotationDegreesDelta(); // Rotate the map - if (mUserLocationView.getMyLocationTrackingMode() == MyLocationTracking.TRACKING_NONE) { + if (mMapboxMap.getTrackingSettings().isLocationTrackingDisabled()) { // around gesture mNativeMapView.setBearing(bearing, detector.getFocusX() / mScreenDensity, detector.getFocusY() / mScreenDensity); } else { - // around center map - mNativeMapView.setBearing(bearing, - (getWidth() / 2) / mScreenDensity, - (getHeight() / 2) / mScreenDensity); + // around center userlocation + PointF centerPoint = mUserLocationView.getMarkerScreenPoint(); + mNativeMapView.setBearing(bearing, centerPoint.x / mScreenDensity, centerPoint.y / mScreenDensity); } return true; } @@ -1986,10 +1944,13 @@ public class MapView extends FrameLayout { return false; } - // reset tracking modes if gesture occurs - resetTrackingModes(); + if (mMapboxMap.getTrackingSettings().isDismissTrackingOnGesture()) { + // reset tracking modes if gesture occurs + resetTrackingModes(); + } mBeginTime = detector.getEventTime(); + trackGestureEvent(MapboxEvent.GESTURE_PITCH_START, detector.getFocusX(), detector.getFocusY()); return true; } @@ -2056,8 +2017,6 @@ public class MapView extends FrameLayout { if (!mMapboxMap.getUiSettings().isZoomGesturesEnabled()) { return; } - - // Zoom in or out zoom(zoomIn); } } @@ -2408,9 +2367,11 @@ public class MapView extends FrameLayout { // Forward to any listeners protected void onMapChanged(int mapChange) { if (mOnMapChangedListener != null) { - int count = mOnMapChangedListener.size(); - for (int i = 0; i < count; i++) { - mOnMapChangedListener.get(i).onMapChanged(mapChange); + OnMapChangedListener listener; + final Iterator<OnMapChangedListener> iterator = mOnMapChangedListener.iterator(); + while (iterator.hasNext()) { + listener = iterator.next(); + listener.onMapChanged(mapChange); } } } @@ -2419,6 +2380,11 @@ public class MapView extends FrameLayout { // User location // + boolean isPermissionsAccepted() { + return (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) || + ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + } + /** * <p> * Enables or disables the my-location layer. @@ -2433,9 +2399,6 @@ public class MapView extends FrameLayout { * @throws SecurityException if no suitable permission is present */ @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) void setMyLocationEnabled(boolean enabled) { mUserLocationView.setEnabled(enabled); } @@ -2473,19 +2436,13 @@ public class MapView extends FrameLayout { * See {@link MyLocationTracking} for different values. * * @param myLocationTrackingMode The location tracking mode to be used. - * @throws SecurityException if no suitable permission is present * @see MyLocationTracking */ @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && !mMapboxMap.isMyLocationEnabled()) { - //noinspection ResourceType mMapboxMap.setMyLocationEnabled(true); } - mUserLocationView.setMyLocationTrackingMode(myLocationTrackingMode); MapboxMap.OnMyLocationTrackingModeChangeListener listener = mMapboxMap.getOnMyLocationTrackingModeChangeListener(); if (listener != null) { @@ -2494,19 +2451,6 @@ public class MapView extends FrameLayout { } /** - * Returns the current user location tracking mode. - * - * @return The current user location tracking mode. - * One of the values from {@link MyLocationTracking.Mode}. - * @see MyLocationTracking.Mode - */ - @UiThread - @MyLocationTracking.Mode - int getMyLocationTrackingMode() { - return mUserLocationView.getMyLocationTrackingMode(); - } - - /** * <p> * Set the current my bearing tracking mode. * </p> @@ -2518,16 +2462,11 @@ public class MapView extends FrameLayout { * See {@link MyBearingTracking} for different values. * * @param myBearingTrackingMode The bearing tracking mode to be used. - * @throws SecurityException if no suitable permission is present * @see MyBearingTracking */ @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { if (myBearingTrackingMode != MyBearingTracking.NONE && !mMapboxMap.isMyLocationEnabled()) { - //noinspection ResourceType mMapboxMap.setMyLocationEnabled(true); } mUserLocationView.setMyBearingTrackingMode(myBearingTrackingMode); @@ -2537,26 +2476,11 @@ public class MapView extends FrameLayout { } } - /** - * Returns the current user bearing tracking mode. - * See {@link MyBearingTracking} for possible return values. - * - * @return the current user bearing tracking mode. - * @see MyBearingTracking - */ - @UiThread - @MyLocationTracking.Mode - int getMyBearingTrackingMode() { - //noinspection ResourceType - return mUserLocationView.getMyBearingTrackingMode(); - } - private void resetTrackingModes() { try { - //noinspection ResourceType - setMyLocationTrackingMode(MyLocationTracking.TRACKING_NONE); - //noinspection ResourceType - setMyBearingTrackingMode(MyBearingTracking.NONE); + TrackingSettings trackingSettings = mMapboxMap.getTrackingSettings(); + trackingSettings.setMyLocationTrackingMode(MyLocationTracking.TRACKING_NONE); + trackingSettings.setMyBearingTrackingMode(MyBearingTracking.NONE); } catch (SecurityException ignore) { // User did not accept location permissions } @@ -2806,6 +2730,10 @@ public class MapView extends FrameLayout { private void setWidgetMargins(@NonNull final View view, int left, int top, int right, int bottom) { LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + left += mContentPaddingLeft; + top += mContentPaddingTop; + right += mContentPaddingRight; + bottom += mContentPaddingBottom; layoutParams.setMargins(left, top, right, bottom); view.setLayoutParams(layoutParams); } @@ -2813,9 +2741,11 @@ public class MapView extends FrameLayout { private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener { private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; + private static final int ATTRIBUTION_INDEX_TELEMETRY_SETTINGS = 3; private MapView mMapView; public AttributionOnClickListener(MapView mapView) { + super(); mMapView = mapView; } @@ -2825,7 +2755,7 @@ public class MapView extends FrameLayout { Context context = v.getContext(); String[] items = context.getResources().getStringArray(R.array.attribution_names); AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle); - builder.setTitle(R.string.attributionsDialogTitle); + builder.setTitle(R.string.mapbox_attributionsDialogTitle); builder.setAdapter(new ArrayAdapter<>(context, R.layout.attribution_list_item, items), this); builder.show(); } @@ -2833,7 +2763,49 @@ public class MapView extends FrameLayout { // Called when someone selects an attribution, 'Improve this map' adds location data to the url @Override public void onClick(DialogInterface dialog, int which) { - Context context = ((Dialog) dialog).getContext(); + final Context context = ((Dialog) dialog).getContext(); + if (which == ATTRIBUTION_INDEX_TELEMETRY_SETTINGS) { + + int array = R.array.attribution_telemetry_options; + if (MapboxEventManager.getMapboxEventManager(context).isTelemetryEnabled()) { + array = R.array.attribution_telemetry_options_already_participating; + } + String[] items = context.getResources().getStringArray(array); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle); + builder.setTitle(R.string.mapbox_attributionTelemetryTitle); + LayoutInflater factory = LayoutInflater.from(context); + View content = factory.inflate(R.layout.attribution_telemetry_view, null); + + ListView lv = (ListView) content.findViewById(R.id.telemetryOptionsList); + lv.setAdapter(new ArrayAdapter<String>(context, R.layout.attribution_list_item, items)); + lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + + builder.setView(content); + final AlertDialog telemDialog = builder.show(); + lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + switch (position) { + case 0: + String url = context.getResources().getStringArray(R.array.attribution_links)[3]; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + context.startActivity(intent); + telemDialog.cancel(); + return; + case 1: + MapboxEventManager.getMapboxEventManager(context).setTelemetryEnabled(false); + telemDialog.cancel(); + return; + case 2: + MapboxEventManager.getMapboxEventManager(context).setTelemetryEnabled(true); + telemDialog.cancel(); + return; + } + } + }); + return; + } String url = context.getResources().getStringArray(R.array.attribution_links)[which]; if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) { LatLng latLng = mMapView.getMapboxMap().getCameraPosition().target; @@ -3044,4 +3016,5 @@ public class MapView extends FrameLayout { void onMapChanged(@MapChange int change); } + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index 757a9bc3d9..40b75d3ce1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -1,20 +1,21 @@ package com.mapbox.mapboxsdk.maps; -import android.Manifest; import android.content.Context; import android.location.Location; import android.os.Bundle; -import android.support.annotation.FloatRange; +import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; import android.support.annotation.UiThread; +import android.support.v4.util.LongSparseArray; import android.text.TextUtils; import android.util.Log; import android.view.View; import com.mapbox.mapboxsdk.annotations.Annotation; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; @@ -23,6 +24,8 @@ import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdate; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; @@ -39,10 +42,12 @@ public class MapboxMap { private MapView mMapView; private UiSettings mUiSettings; + private TrackingSettings mTrackingSettings; private Projection mProjection; private CameraPosition mCameraPosition; private boolean mInvalidCameraPosition; private String mStyleUrl; + private LongSparseArray<Annotation> mAnnotations; private List<Marker> mSelectedMarkers; private List<InfoWindow> mInfoWindows; private MapboxMap.InfoWindowAdapter mInfoWindowAdapter; @@ -54,17 +59,22 @@ public class MapboxMap { private MapboxMap.OnMapLongClickListener mOnMapLongClickListener; private MapboxMap.OnMarkerClickListener mOnMarkerClickListener; private MapboxMap.OnInfoWindowClickListener mOnInfoWindowClickListener; + private MapboxMap.OnInfoWindowLongClickListener mOnInfoWindowLongClickListener; + private MapboxMap.OnInfoWindowCloseListener mOnInfoWindowCloseListener; private MapboxMap.OnFlingListener mOnFlingListener; private MapboxMap.OnScrollListener mOnScrollListener; private MapboxMap.OnMyLocationTrackingModeChangeListener mOnMyLocationTrackingModeChangeListener; private MapboxMap.OnMyBearingTrackingModeChangeListener mOnMyBearingTrackingModeChangeListener; private MapboxMap.OnFpsChangedListener mOnFpsChangedListener; + private MapboxMap.OnCameraChangeListener mOnCameraChangeListener; MapboxMap(@NonNull MapView mapView) { mMapView = mapView; mMapView.addOnMapChangedListener(new MapChangeCameraPositionListener()); mUiSettings = new UiSettings(mapView); + mTrackingSettings = new TrackingSettings(mMapView, mUiSettings); mProjection = new Projection(mapView); + mAnnotations = new LongSparseArray<>(); mSelectedMarkers = new ArrayList<>(); mInfoWindows = new ArrayList<>(); } @@ -83,6 +93,19 @@ public class MapboxMap { } // + // TrackingSettings + // + + /** + * Gets the tracking interface settings for the map. + * + * @return + */ + public TrackingSettings getTrackingSettings() { + return mTrackingSettings; + } + + // // Projection // @@ -105,14 +128,7 @@ public class MapboxMap { */ public final CameraPosition getCameraPosition() { if (mInvalidCameraPosition) { - // Camera position has changed, need to regenerate position - mCameraPosition = new CameraPosition.Builder(true) - .bearing((float) mMapView.getBearing()) - .target(mMapView.getLatLng()) - .tilt((float) mMapView.getTilt()) - .zoom((float) mMapView.getZoom()) - .build(); - mInvalidCameraPosition = false; + invalidateCameraPosition(); } return mCameraPosition; } @@ -137,8 +153,26 @@ public class MapboxMap { */ @UiThread public final void moveCamera(CameraUpdate update) { + moveCamera(update, null); + } + + /** + * Repositions the camera according to the instructions defined in the update. + * The move is instantaneous, and a subsequent getCameraPosition() will reflect the new position. + * See CameraUpdateFactory for a set of updates. + * + * @param update The change that should be applied to the camera. + */ + @UiThread + public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); mMapView.jumpTo(mCameraPosition.bearing, mCameraPosition.target, mCameraPosition.tilt, mCameraPosition.zoom); + if (mOnCameraChangeListener != null) { + mOnCameraChangeListener.onCameraChange(mCameraPosition); + } + if (callback != null) { + callback.onFinish(); + } } /** @@ -175,7 +209,25 @@ public class MapboxMap { @UiThread public final void easeCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); - mMapView.easeTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, callback); + mMapView.easeTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, new CancelableCallback() { + @Override + public void onCancel() { + if (callback != null) { + callback.onCancel(); + } + } + + @Override + public void onFinish() { + if (mOnCameraChangeListener != null) { + mOnCameraChangeListener.onCameraChange(mCameraPosition); + } + + if (callback != null) { + callback.onFinish(); + } + } + }); } /** @@ -227,7 +279,25 @@ public class MapboxMap { @UiThread public final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { mCameraPosition = update.getCameraPosition(this); - mMapView.flyTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, callback); + mMapView.flyTo(mCameraPosition.bearing, mCameraPosition.target, getDurationNano(durationMs), mCameraPosition.tilt, mCameraPosition.zoom, new CancelableCallback() { + @Override + public void onCancel() { + if (callback != null) { + callback.onCancel(); + } + } + + @Override + public void onFinish() { + if (mOnCameraChangeListener != null) { + mOnCameraChangeListener.onCameraChange(mCameraPosition); + } + + if (callback != null) { + callback.onFinish(); + } + } + }); } // internal time layer conversion @@ -235,75 +305,34 @@ public class MapboxMap { return durationMs > 0 ? TimeUnit.NANOSECONDS.convert(durationMs, TimeUnit.MILLISECONDS) : 0; } - // - // ZOOM - // - - /** - * <p> - * Sets the minimum zoom level the map can be displayed at. - * </p> - * - * @param minZoom The new minimum zoom level. - */ - @UiThread - public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) { - if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) { - Log.e(MapboxConstants.TAG, "Not setting minZoom, value is in unsupported range: " + minZoom); - return; + private void invalidateCameraPosition() { + mInvalidCameraPosition = false; + mCameraPosition = new CameraPosition.Builder(true) + .bearing((float) mMapView.getBearing()) + .target(mMapView.getLatLng()) + .tilt((float) mMapView.getTilt()) + .zoom((float) mMapView.getZoom()) + .build(); + if (mOnCameraChangeListener != null) { + mOnCameraChangeListener.onCameraChange(mCameraPosition); } - mMapView.setMinZoom(minZoom); } - /** - * <p> - * Gets the maximum zoom level the map can be displayed at. - * </p> - * - * @return The minimum zoom level. - */ - @UiThread - public double getMinZoom() { - return mMapView.getMinZoom(); - } - - /** - * <p> - * Sets the maximum zoom level the map can be displayed at. - * </p> - * - * @param maxZoom The new maximum zoom level. - */ - @UiThread - public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { - if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) { - Log.e(MapboxConstants.TAG, "Not setting maxZoom, value is in unsupported range: " + maxZoom); - return; - } - mMapView.setMaxZoom(maxZoom); - } + // + // Reset North + // /** - * <p> - * Gets the maximum zoom level the map can be displayed at. - * </p> * - * @return The maximum zoom level. */ - @UiThread - public double getMaxZoom() { - return mMapView.getMaxZoom(); + public void resetNorth() { + mMapView.resetNorth(); } // // Manual zoom controls // - // used by UiSettings - void setZoomControlsEnabled(boolean enabled) { - mMapView.setZoomControlsEnabled(enabled); - } - // // Debug // @@ -468,7 +497,17 @@ public class MapboxMap { @UiThread @NonNull public Marker addMarker(@NonNull MarkerOptions markerOptions) { - return mMapView.addMarker(markerOptions); + return addMarker((BaseMarkerOptions) markerOptions); + } + + @UiThread + @NonNull + public Marker addMarker(@NonNull BaseMarkerOptions markerOptions) { + Marker marker = prepareMarker(markerOptions); + long id = mMapView.addMarker(marker); + marker.setId(id); + mAnnotations.put(id, marker); + return marker; } /** @@ -484,7 +523,50 @@ public class MapboxMap { @UiThread @NonNull public List<Marker> addMarkers(@NonNull List<MarkerOptions> markerOptionsList) { - return mMapView.addMarkers(markerOptionsList); + int count = markerOptionsList.size(); + List<Marker> markers = new ArrayList<>(count); + MarkerOptions markerOptions; + Marker marker; + for (int i = 0; i < count; i++) { + markerOptions = markerOptionsList.get(i); + marker = prepareMarker(markerOptions); + markers.add(marker); + } + + long[] ids = mMapView.addMarkers(markers); + long id = 0; + Marker m; + + for (int i = 0; i < markers.size(); i++) { + m = markers.get(i); + m.setMapboxMap(this); + if (ids != null) { + id = ids[i]; + } else { + //unit test + id++; + } + m.setId(id); + mAnnotations.put(id, m); + } + return markers; + } + + /** + * <p> + * Updates a marker on this map. Does nothing if the marker is already added. + * </p> + * + * @param updatedMarker An updated marker object. + */ + @UiThread + public void updateMarker(@NonNull Marker updatedMarker) { + mMapView.updateMarker(updatedMarker); + + int index = mAnnotations.indexOfKey(updatedMarker.getId()); + if (index > -1) { + mAnnotations.setValueAt(index, updatedMarker); + } } /** @@ -496,7 +578,14 @@ public class MapboxMap { @UiThread @NonNull public Polyline addPolyline(@NonNull PolylineOptions polylineOptions) { - return mMapView.addPolyline(polylineOptions); + Polyline polyline = polylineOptions.getPolyline(); + if (!polyline.getPoints().isEmpty()) { + long id = mMapView.addPolyline(polyline); + polyline.setMapboxMap(this); + polyline.setId(id); + mAnnotations.put(id, polyline); + } + return polyline; } /** @@ -508,7 +597,33 @@ public class MapboxMap { @UiThread @NonNull public List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList) { - return mMapView.addPolylines(polylineOptionsList); + int count = polylineOptionsList.size(); + Polyline polyline; + List<Polyline> polylines = new ArrayList<>(count); + for (PolylineOptions options : polylineOptionsList) { + polyline = options.getPolyline(); + if (!polyline.getPoints().isEmpty()) { + polylines.add(polyline); + } + } + + long[] ids = mMapView.addPolylines(polylines); + long id = 0; + Polyline p; + + for (int i = 0; i < polylines.size(); i++) { + p = polylines.get(i); + p.setMapboxMap(this); + if (ids != null) { + id = ids[i]; + } else { + // unit test + id++; + } + p.setId(id); + mAnnotations.put(id, p); + } + return polylines; } /** @@ -520,7 +635,14 @@ public class MapboxMap { @UiThread @NonNull public Polygon addPolygon(@NonNull PolygonOptions polygonOptions) { - return mMapView.addPolygon(polygonOptions); + Polygon polygon = polygonOptions.getPolygon(); + if (!polygon.getPoints().isEmpty()) { + long id = mMapView.addPolygon(polygon); + polygon.setId(id); + polygon.setMapboxMap(this); + mAnnotations.put(id, polygon); + } + return polygon; } /** @@ -532,7 +654,32 @@ public class MapboxMap { @UiThread @NonNull public List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList) { - return mMapView.addPolygons(polygonOptionsList); + int count = polygonOptionsList.size(); + + Polygon polygon; + List<Polygon> polygons = new ArrayList<>(count); + for (PolygonOptions polygonOptions : polygonOptionsList) { + polygon = polygonOptions.getPolygon(); + if (!polygon.getPoints().isEmpty()) { + polygons.add(polygon); + } + } + + long[] ids = mMapView.addPolygons(polygons); + long id = 0; + for (int i = 0; i < polygons.size(); i++) { + polygon = polygons.get(i); + polygon.setMapboxMap(this); + if (ids != null) { + id = ids[i]; + } else { + // unit test + id++; + } + polygon.setId(id); + mAnnotations.put(id, polygon); + } + return polygons; } /** @@ -549,13 +696,55 @@ public class MapboxMap { } /** + * <p> + * Convenience method for removing a Polyline from the map. + * </p> + * Calls removeAnnotation() internally + * + * @param polyline Polyline to remove + */ + @UiThread + public void removePolyline(@NonNull Polyline polyline) { + removeAnnotation(polyline); + } + + /** + * <p> + * Convenience method for removing a Polygon from the map. + * </p> + * Calls removeAnnotation() internally + * + * @param polygon Polygon to remove + */ + @UiThread + public void removePolygon(@NonNull Polygon polygon) { + removeAnnotation(polygon); + } + + /** * Removes an annotation from the map. * * @param annotation The annotation object to remove. */ @UiThread public void removeAnnotation(@NonNull Annotation annotation) { - mMapView.removeAnnotation(annotation); + if (annotation instanceof Marker) { + ((Marker) annotation).hideInfoWindow(); + } + long id = annotation.getId(); + mMapView.removeAnnotation(id); + mAnnotations.remove(id); + } + + /** + * Removes an annotation from the map + * + * @param id The identifier associated to the annotation to be removed + */ + @UiThread + public void removeAnnotation(long id) { + mMapView.removeAnnotation(id); + mAnnotations.remove(id); } /** @@ -565,15 +754,49 @@ public class MapboxMap { */ @UiThread public void removeAnnotations(@NonNull List<? extends Annotation> annotationList) { - mMapView.removeAnnotations(annotationList); + int count = annotationList.size(); + long[] ids = new long[count]; + for (int i = 0; i < count; i++) { + Annotation annotation = annotationList.get(i); + if (annotation instanceof Marker) { + ((Marker) annotation).hideInfoWindow(); + } + ids[i] = annotationList.get(i).getId(); + } + mMapView.removeAnnotations(ids); + for (long id : ids) { + mAnnotations.remove(id); + } } /** * Removes all annotations from the map. */ @UiThread - public void removeAllAnnotations() { - mMapView.removeAllAnnotations(); + public void removeAnnotations() { + Annotation annotation; + int count = mAnnotations.size(); + long[] ids = new long[count]; + for (int i = 0; i < count; i++) { + ids[i] = mAnnotations.keyAt(i); + annotation = mAnnotations.get(ids[i]); + if (annotation instanceof Marker) { + ((Marker) annotation).hideInfoWindow(); + } + } + mMapView.removeAnnotations(ids); + mAnnotations.clear(); + } + + /** + * Return a annotation based on its id. + * + * @return An annotation with a matched id, null is returned if no match was found. + */ + @UiThread + @Nullable + public Annotation getAnnotation(long id) { + return mAnnotations.get(id); } /** @@ -583,8 +806,69 @@ public class MapboxMap { * list will not update the map. */ @NonNull - public List<Annotation> getAllAnnotations() { - return mMapView.getAllAnnotations(); + public List<Annotation> getAnnotations() { + List<Annotation> annotations = new ArrayList<>(); + for (int i = 0; i < mAnnotations.size(); i++) { + annotations.add(mAnnotations.get(mAnnotations.keyAt(i))); + } + return annotations; + } + + /** + * Returns a list of all the markers on the map. + * + * @return A list of all the markers objects. The returned object is a copy so modifying this + * list will not update the map. + */ + @NonNull + public List<Marker> getMarkers() { + List<Marker> markers = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < mAnnotations.size(); i++) { + annotation = mAnnotations.get(mAnnotations.keyAt(i)); + if (annotation instanceof Marker) { + markers.add((Marker) annotation); + } + } + return markers; + } + + /** + * Returns a list of all the polygons on the map. + * + * @return A list of all the polygon objects. The returned object is a copy so modifying this + * list will not update the map. + */ + @NonNull + public List<Polygon> getPolygons() { + List<Polygon> polygons = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < mAnnotations.size(); i++) { + annotation = mAnnotations.get(mAnnotations.keyAt(i)); + if (annotation instanceof Polygon) { + polygons.add((Polygon) annotation); + } + } + return polygons; + } + + /** + * Returns a list of all the polylines on the map. + * + * @return A list of all the polylines objects. The returned object is a copy so modifying this + * list will not update the map. + */ + @NonNull + public List<Polyline> getPolylines() { + List<Polyline> polylines = new ArrayList<>(); + Annotation annotation; + for (int i = 0; i < mAnnotations.size(); i++) { + annotation = mAnnotations.get(mAnnotations.keyAt(i)); + if (annotation instanceof Polyline) { + polylines.add((Polyline) annotation); + } + } + return polylines; } /** @@ -670,11 +954,17 @@ public class MapboxMap { * @return The currently selected marker. */ @UiThread - @Nullable public List<Marker> getSelectedMarkers() { return mSelectedMarkers; } + private Marker prepareMarker(BaseMarkerOptions markerOptions) { + Marker marker = markerOptions.getMarker(); + Icon icon = mMapView.loadIconForMarker(marker); + marker.setTopOffsetPixels(mMapView.getTopOffsetPixelsForIcon(icon)); + return marker; + } + // // InfoWindow // @@ -735,10 +1025,58 @@ public class MapboxMap { } // + // Padding + // + + /** + * Sets the distance from the edges of the map view’s frame to the edges of the map + * view’s logical viewport. + * <p/> + * When the value of this property is equal to {0,0,0,0}, viewport + * properties such as `centerCoordinate` assume a viewport that matches the map + * view’s frame. Otherwise, those properties are inset, excluding part of the + * frame from the viewport. For instance, if the only the top edge is inset, the + * map center is effectively shifted downward. + * + * @param left The left margin in pixels. + * @param top The top margin in pixels. + * @param right The right margin in pixels. + * @param bottom The bottom margin in pixels. + */ + public void setPadding(int left, int top, int right, int bottom) { + mMapView.setContentPadding(left, top, right, bottom); + mUiSettings.invalidate(); + + moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder(mCameraPosition).build())); + } + + /** + * + * @return + */ + public int[] getPadding() { + return new int[]{mMapView.getContentPaddingLeft(), + mMapView.getContentPaddingTop(), + mMapView.getContentPaddingRight(), + mMapView.getContentPaddingBottom()}; + } + + // // Map events // /** + * Sets a callback that's invoked on every change in camera position. + * + * @param listener The callback that's invoked on every camera change position. + * To unset the callback, use null. + */ + @UiThread + public void setOnCameraChangeListener(@Nullable OnCameraChangeListener listener) { + mOnCameraChangeListener = listener; + } + + /** * Sets a callback that's invoked on every frame rendered to the map view. * * @param listener The callback that's invoked on every frame rendered to the map view. @@ -845,10 +1183,44 @@ public class MapboxMap { * * @return Current active InfoWindow Click Listener */ + @UiThread public OnInfoWindowClickListener getOnInfoWindowClickListener() { return mOnInfoWindowClickListener; } + /** + * Sets a callback that's invoked when a marker's info window is long pressed. + * + * @param listener The callback that's invoked when a marker's info window is long pressed. To unset the callback, use null. + */ + @UiThread + public void setOnInfoWindowLongClickListener(@Nullable OnInfoWindowLongClickListener listener) { + mOnInfoWindowLongClickListener = listener; + } + + /** + * Return the InfoWindow long click listener + * + * @return Current active InfoWindow long Click Listener + */ + public OnInfoWindowLongClickListener getOnInfoWindowLongClickListener() { + return mOnInfoWindowLongClickListener; + } + + public void setOnInfoWindowCloseListener(@Nullable OnInfoWindowCloseListener listener) { + mOnInfoWindowCloseListener = listener; + } + + /** + * Return the InfoWindow close listener + * + * @return Current active InfoWindow Close Listener + */ + @UiThread + public OnInfoWindowCloseListener getOnInfoWindowCloseListener() { + return mOnInfoWindowCloseListener; + } + // // User location // @@ -874,13 +1246,14 @@ public class MapboxMap { * or @link android.Manifest.permission#ACCESS_FINE_LOCATION. * * @param enabled True to enable; false to disable. - * @throws SecurityException if no suitable permission is present */ @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) public void setMyLocationEnabled(boolean enabled) { + if (!mMapView.isPermissionsAccepted()) { + Log.e(MapboxConstants.TAG, "Could not activate user location tracking: " + + "user did not accept the permission or permissions were not requested."); + return; + } mMyLocationEnabled = enabled; mMapView.setMyLocationEnabled(enabled); } @@ -909,40 +1282,6 @@ public class MapboxMap { } /** - * <p> - * Set the current my location tracking mode. - * </p> - * <p> - * Will enable my location if not active. - * </p> - * See {@link MyLocationTracking} for different values. - * - * @param myLocationTrackingMode The location tracking mode to be used. - * @throws SecurityException if no suitable permission is present - * @see MyLocationTracking - */ - @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) - public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { - mMapView.setMyLocationTrackingMode(myLocationTrackingMode); - } - - /** - * Returns the current user location tracking mode. - * - * @return The current user location tracking mode. - * One of the values from {@link MyLocationTracking.Mode}. - * @see MyLocationTracking.Mode - */ - @UiThread - @MyLocationTracking.Mode - public int getMyLocationTrackingMode() { - return mMapView.getMyLocationTrackingMode(); - } - - /** * Sets a callback that's invoked when the location tracking mode changes. * * @param listener The callback that's invoked when the location tracking mode changes. @@ -959,42 +1298,6 @@ public class MapboxMap { } /** - * <p> - * Set the current my bearing tracking mode. - * </p> - * Shows the direction the user is heading. - * <p> - * When location tracking is disabled the direction of {@link UserLocationView} is rotated - * When location tracking is enabled the {@link MapView} is rotated based on bearing value. - * </p> - * See {@link MyBearingTracking} for different values. - * - * @param myBearingTrackingMode The bearing tracking mode to be used. - * @throws SecurityException if no suitable permission is present - * @see MyBearingTracking - */ - @UiThread - @RequiresPermission(anyOf = { - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION}) - public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { - mMapView.setMyBearingTrackingMode(myBearingTrackingMode); - } - - /** - * Returns the current user bearing tracking mode. - * See {@link MyBearingTracking} for possible return values. - * - * @return the current user bearing tracking mode. - * @see MyBearingTracking - */ - @UiThread - @MyLocationTracking.Mode - public int getMyBearingTrackingMode() { - return mMapView.getMyBearingTrackingMode(); - } - - /** * Sets a callback that's invoked when the bearing tracking mode changes. * * @param listener The callback that's invoked when the bearing tracking mode changes. @@ -1033,9 +1336,17 @@ public class MapboxMap { return mMapView; } -// -// Interfaces -// + // + // Invalidate + // + + public void invalidate(){ + mMapView.update(); + } + + // + // Interfaces + // /** * Interface definition for a callback to be invoked when the map is flinged. @@ -1062,6 +1373,20 @@ public class MapboxMap { } /** + * Interface definition for a callback to be invoked for when the camera changes position. + */ + public interface OnCameraChangeListener { + /** + * Called after the camera position has changed. During an animation, + * this listener may not be notified of intermediate camera positions. + * It is always called for the final position in the animation. + * + * @param position The CameraPosition at the end of the last camera change. + */ + void onCameraChange(CameraPosition position); + } + + /** * Interface definition for a callback to be invoked on every frame rendered to the map view. * * @see MapboxMap#setOnFpsChangedListener(OnFpsChangedListener) @@ -1130,7 +1455,37 @@ public class MapboxMap { * @param marker The marker of the info window the user clicked on. * @return If true the listener has consumed the event and the info window will not be closed. */ - boolean onMarkerClick(@NonNull Marker marker); + boolean onInfoWindowClick(@NonNull Marker marker); + } + + /** + * Callback interface for when the user long presses on a marker's info window. + * + * @see MapboxMap#setOnInfoWindowClickListener(OnInfoWindowClickListener) + */ + public interface OnInfoWindowLongClickListener { + + /** + * Called when the user makes a long-press gesture on the marker's info window. + * + * @param marker The marker were the info window is attached to + */ + void onInfoWindowLongClick(Marker marker); + } + + /** + * Callback interface for close events on a marker's info window. + * + * @see MapboxMap#setOnInfoWindowCloseListener(OnInfoWindowCloseListener) + */ + public interface OnInfoWindowCloseListener { + + /** + * Called when the marker's info window is closed. + * + * @param marker The marker of the info window that was closed. + */ + void onInfoWindowClose(Marker marker); } /** @@ -1169,7 +1524,7 @@ public class MapboxMap { /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * - * @see MapboxMap#setMyLocationTrackingMode(int) + * @see MapView#setMyLocationTrackingMode(int) */ public interface OnMyLocationTrackingModeChangeListener { @@ -1184,7 +1539,7 @@ public class MapboxMap { /** * Interface definition for a callback to be invoked when the the My Location tracking mode changes. * - * @see MapboxMap#setMyLocationTrackingMode(int) + * @see MapView#setMyLocationTrackingMode(int) */ public interface OnMyBearingTrackingModeChangeListener { @@ -1212,13 +1567,20 @@ public class MapboxMap { } private class MapChangeCameraPositionListener implements MapView.OnMapChangedListener { + + private static final long UPDATE_RATE_MS = 400; + private long mPreviousUpdateTimestamp = 0; + @Override public void onMapChanged(@MapView.MapChange int change) { - if (!mInvalidCameraPosition && (change == MapView.REGION_DID_CHANGE - || change == MapView.REGION_DID_CHANGE_ANIMATED - || change == MapView.REGION_WILL_CHANGE - || change == MapView.REGION_WILL_CHANGE_ANIMATED)) { + if (change >= MapView.REGION_WILL_CHANGE && change <= MapView.REGION_DID_CHANGE_ANIMATED) { mInvalidCameraPosition = true; + long currentTime = SystemClock.elapsedRealtime(); + if (currentTime < mPreviousUpdateTimestamp) { + return; + } + invalidateCameraPosition(); + mPreviousUpdateTimestamp = currentTime + UPDATE_RATE_MS; } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java deleted file mode 100644 index cb6407986e..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.mapbox.mapboxsdk.maps; - -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; - -import com.mapbox.mapboxsdk.camera.CameraPosition; - -/** - * Builder for composing {@link MapboxMap} objects. These options can be used when adding a - * map to your application programmatically (as opposed to via XML). If you are using a MapFragment, - * you can pass these options in using the static factory method newInstance(MapboxMapOptions). - * If you are using a MapView, you can pass these options in using the constructor MapView(Context, MapboxMapOptions). - */ -public class MapboxMapOptions implements Parcelable { - - private MapboxMap mMapboxMap; - private UiSettings mUiSettings; - - public MapboxMapOptions(MapboxMap mapboxMap) { - mMapboxMap = mapboxMap; - mUiSettings = mapboxMap.getUiSettings(); - } - - public MapboxMapOptions(Parcel in) { - throw new UnsupportedOperationException(); - } - - public MapboxMapOptions camera(CameraPosition camera) { - mMapboxMap.setCameraPosition(camera); - return this; - } - - public CameraPosition getCamera() { - return mMapboxMap.getCameraPosition(); - } - - public MapboxMapOptions compassEnabled(boolean enabled) { - mUiSettings.setCompassEnabled(enabled); - return this; - } - - public boolean getCompassEnabled() { - return mUiSettings.isCompassEnabled(); - } - - public MapboxMapOptions rotateEnabled(boolean rotateEnabled) { - mUiSettings.setRotateGesturesEnabled(rotateEnabled); - return this; - } - - public MapboxMapOptions rotateGesturesEnabled(boolean enabled) { - mUiSettings.setRotateGesturesEnabled(enabled); - return this; - } - - public boolean getRotateGesturesEnabled() { - return mUiSettings.isRotateGesturesEnabled(); - } - - public MapboxMapOptions scrollGesturesEnabled(boolean enabled) { - mUiSettings.setScrollGesturesEnabled(enabled); - return this; - } - - public boolean getScrollGesturesEnabled() { - return mUiSettings.isScrollGesturesEnabled(); - } - - public MapboxMapOptions tiltGesturesEnabled(boolean enabled) { - mUiSettings.setTiltGesturesEnabled(enabled); - return this; - } - - public boolean getTiltGesturesEnabled() { - return mUiSettings.isTiltGesturesEnabled(); - } - - public MapboxMapOptions zoomControlsEnabled(boolean enabled) { - mUiSettings.setZoomControlsEnabled(enabled); - return this; - } - - public boolean getZoomControlsEnabled() { - return mUiSettings.isZoomControlsEnabled(); - } - - public boolean getZoomGesturesEnabled() { - return mUiSettings.isZoomGesturesEnabled(); - } - - public MapboxMapOptions createFromAttributes(Context context, AttributeSet attrs) { - throw new UnsupportedOperationException(); - } - - public static final Parcelable.Creator<MapboxMapOptions> CREATOR = new Parcelable.Creator<MapboxMapOptions>() { - public MapboxMapOptions createFromParcel(Parcel in) { - return new MapboxMapOptions(in); - } - - public MapboxMapOptions[] newArray(int size) { - return new MapboxMapOptions[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - throw new UnsupportedOperationException(); - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 6c388d9d8a..b7f583e943 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -395,6 +395,10 @@ final class NativeMapView { return nativeAddPolygons(mNativeMapViewPtr, polygon); } + public void updateMarker(Marker marker) { + nativeUpdateMarker(mNativeMapViewPtr, marker); + } + public void removeAnnotation(long id) { nativeRemoveAnnotation(mNativeMapViewPtr, id); } @@ -619,6 +623,8 @@ final class NativeMapView { private native long nativeAddMarker(long nativeMapViewPtr, Marker marker); + private native void nativeUpdateMarker(long nativeMapViewPtr, Marker marker); + private native long[] nativeAddMarkers(long nativeMapViewPtr, List<Marker> markers); private native long nativeAddPolyline(long nativeMapViewPtr, Polyline polyline); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index e53d430b69..0d5745d4c9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.maps; import android.graphics.PointF; +import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -21,6 +22,20 @@ public class Projection { } /** + * <p> + * Returns the distance spanned by one pixel at the specified latitude and current zoom level. + * </p> + * The distance between pixels decreases as the latitude approaches the poles. + * This relationship parallels the relationship between longitudinal coordinates at different latitudes. + * + * @param latitude The latitude for which to return the value. + * @return The distance measured in meters. + */ + public double getMetersPerPixelAtLatitude(@FloatRange(from = -180, to = 180) double latitude) { + return mMapView.getMetersPerPixelAtLatitude(latitude); + } + + /** * Returns the geographic location that corresponds to a screen location. * The screen location is specified in screen pixels (not display pixels) relative to the * top left of the map (not the top left of the whole screen). @@ -55,7 +70,7 @@ public class Projection { .include(bottomRight) .include(bottomLeft); - return new VisibleRegion(topLeft,topRight,bottomLeft,bottomRight,builder.build()); + return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, builder.build()); } /** @@ -69,4 +84,14 @@ public class Projection { public PointF toScreenLocation(LatLng location) { return mMapView.toScreenLocation(location); } + + /** + * Calculates a zoom level based on minimum scale and current scale from MapView + * + * @param minScale The minimum scale to calculate the zoom level. + * @return zoom level that fits the MapView. + */ + public double calculateZoom(float minScale) { + return Math.log(mMapView.getScale() * minScale) / Math.log(2); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java index 147cd31b5a..8783712e10 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.maps; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -29,18 +30,10 @@ public class SupportMapFragment extends Fragment { private MapView mMap; - public static SupportMapFragment newInstance(){ + public static SupportMapFragment newInstance() { return new SupportMapFragment(); } - public static SupportMapFragment newInstance(MapboxMapOptions mapboxMapOptions) { - final SupportMapFragment mapFragment = new SupportMapFragment(); - Bundle bundle = new Bundle(); - bundle.putParcelable(MapboxConstants.FRAG_ARG_MAPBOXMAPOPTIONS, mapboxMapOptions); - mapFragment.setArguments(bundle); - return mapFragment; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); @@ -94,7 +87,12 @@ public class SupportMapFragment extends Fragment { } @NonNull - public void getMapAsync(@NonNull OnMapReadyCallback onMapReadyCallback){ - mMap.getMapAsync(onMapReadyCallback); + public void getMapAsync(@NonNull final OnMapReadyCallback onMapReadyCallback) { + new Handler().post(new Runnable() { + @Override + public void run() { + mMap.getMapAsync(onMapReadyCallback); + } + }); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java new file mode 100644 index 0000000000..543ff19e56 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java @@ -0,0 +1,121 @@ +package com.mapbox.mapboxsdk.maps; + +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import com.mapbox.mapboxsdk.constants.MyBearingTracking; +import com.mapbox.mapboxsdk.constants.MyLocationTracking; + +public class TrackingSettings { + + private MapView mapView; + private UiSettings uiSettings; + private boolean dismissTrackingOnGesture = true; + + @MyLocationTracking.Mode + private int mMyLocationTrackingMode; + + @MyBearingTracking.Mode + private int mMyBearingTrackingMode; + + TrackingSettings(@NonNull MapView mapView, UiSettings uiSettings) { + this.mapView = mapView; + this.uiSettings = uiSettings; + } + + /** + * <p> + * Set the current my location tracking mode. + * </p> + * <p> + * Will enable my location if not active. + * </p> + * See {@link MyLocationTracking} for different values. + * + * @param myLocationTrackingMode The location tracking mode to be used. + * @throws SecurityException if no suitable permission is present + * @see MyLocationTracking + */ + @UiThread + public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { + mMyLocationTrackingMode = myLocationTrackingMode; + mapView.setMyLocationTrackingMode(myLocationTrackingMode); + validateGesturesForTrackingModes(); + } + + /** + * Returns the current user location tracking mode. + * + * @return The current user location tracking mode. + * One of the values from {@link MyLocationTracking.Mode}. + * @see MyLocationTracking.Mode + */ + @UiThread + @MyLocationTracking.Mode + public int getMyLocationTrackingMode() { + return mMyLocationTrackingMode; + } + + /** + * <p> + * Set the current my bearing tracking mode. + * </p> + * Shows the direction the user is heading. + * <p> + * When location tracking is disabled the direction of {@link UserLocationView} is rotated + * When location tracking is enabled the {@link MapView} is rotated based on bearing value. + * </p> + * See {@link MyBearingTracking} for different values. + * + * @param myBearingTrackingMode The bearing tracking mode to be used. + * @throws SecurityException if no suitable permission is present + * @see MyBearingTracking + */ + @UiThread + public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { + mMyBearingTrackingMode = myBearingTrackingMode; + mapView.setMyBearingTrackingMode(myBearingTrackingMode); + } + + /** + * Returns the current user bearing tracking mode. + * See {@link MyBearingTracking} for possible return values. + * + * @return the current user bearing tracking mode. + * @see MyBearingTracking + */ + @UiThread + @MyLocationTracking.Mode + public int getMyBearingTrackingMode() { + return mMyBearingTrackingMode; + } + + public boolean isDismissTrackingOnGesture() { + return dismissTrackingOnGesture; + } + + public void setDismissTrackingOnGesture(boolean dismissTrackingOnGesture) { + this.dismissTrackingOnGesture = dismissTrackingOnGesture; + validateGesturesForTrackingModes(); + } + + private void validateGesturesForTrackingModes() { + if (!dismissTrackingOnGesture) { + int myLocationTrackingMode = getMyLocationTrackingMode(); + int myBearingTrackingMode = getMyBearingTrackingMode(); + + // Enable/disable gestures based on tracking mode + if (myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { + uiSettings.setScrollGesturesEnabled(true); + uiSettings.setRotateGesturesEnabled(true); + } else { + uiSettings.setScrollGesturesEnabled(false); + uiSettings.setRotateGesturesEnabled((myBearingTrackingMode == MyBearingTracking.NONE)); + } + } + } + + public boolean isLocationTrackingDisabled(){ + return mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java index d6cb106054..1538f49d60 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java @@ -1,9 +1,14 @@ package com.mapbox.mapboxsdk.maps; +import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.UiThread; +import android.util.Log; import android.view.Gravity; import android.view.View; +import android.widget.VideoView; + +import com.mapbox.mapboxsdk.constants.MapboxConstants; /** * Settings for the user interface of a MapboxMap. To obtain this interface, call getUiSettings(). @@ -12,17 +17,9 @@ public class UiSettings { private MapView mapView; - private boolean compassEnabled; - private int compassGravity; - private int[] compassMargins; - - private boolean logoEnabled; - private int logoGravity; - private int[] logoMargins; - - private boolean attributionEnabled; - private int attributionGravity; - private int[] attributionMargins; + private ViewSettings compassSettings; + private ViewSettings logoSettings; + private ViewSettings attributionSettings; private boolean rotateGesturesEnabled; private boolean tiltGesturesEnabled; @@ -30,11 +27,78 @@ public class UiSettings { private boolean zoomControlsEnabled; private boolean scrollGesturesEnabled; + private double maxZoomLevel = -1; + private double minZoomLevel = -1; + UiSettings(@NonNull MapView mapView) { this.mapView = mapView; - this.compassMargins = new int[4]; - this.attributionMargins = new int[4]; - this.logoMargins = new int[4]; + this.compassSettings = new ViewSettings(); + this.logoSettings = new ViewSettings(); + this.attributionSettings = new ViewSettings(); + } + + /** + * <p> + * Sets the minimum zoom level the map can be displayed at. + * </p> + * + * @param minZoom The new minimum zoom level. + */ + @UiThread + public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) { + if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) { + Log.e(MapboxConstants.TAG, "Not setting minZoom, value is in unsupported range: " + minZoom); + return; + } + minZoomLevel = minZoom; + mapView.setMinZoom(minZoom); + } + + /** + * <p> + * Gets the maximum zoom level the map can be displayed at. + * </p> + * + * @return The minimum zoom level. + */ + @UiThread + public double getMinZoom() { + if (minZoomLevel == -1) { + return minZoomLevel = mapView.getMinZoom(); + } + return minZoomLevel; + } + + /** + * <p> + * Sets the maximum zoom level the map can be displayed at. + * </p> + * + * @param maxZoom The new maximum zoom level. + */ + @UiThread + public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) { + if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) { + Log.e(MapboxConstants.TAG, "Not setting maxZoom, value is in unsupported range: " + maxZoom); + return; + } + maxZoomLevel = maxZoom; + mapView.setMaxZoom(maxZoom); + } + + /** + * <p> + * Gets the maximum zoom level the map can be displayed at. + * </p> + * + * @return The maximum zoom level. + */ + @UiThread + public double getMaxZoom() { + if (maxZoomLevel == -1) { + return maxZoomLevel = mapView.getMaxZoom(); + } + return maxZoomLevel; } /** @@ -49,8 +113,8 @@ public class UiSettings { * @param compassEnabled True to enable the compass; false to disable the compass. */ public void setCompassEnabled(boolean compassEnabled) { - this.compassEnabled = compassEnabled; - this.mapView.setCompassEnabled(compassEnabled); + compassSettings.setEnabled(compassEnabled); + mapView.setCompassEnabled(compassEnabled); } /** @@ -59,7 +123,7 @@ public class UiSettings { * @return True if the compass is enabled; false if the compass is disabled. */ public boolean isCompassEnabled() { - return compassEnabled; + return compassSettings.isEnabled(); } /** @@ -74,8 +138,8 @@ public class UiSettings { */ @UiThread public void setCompassGravity(int gravity) { - this.compassGravity = gravity; - this.mapView.setCompassGravity(gravity); + compassSettings.setGravity(gravity); + mapView.setCompassGravity(gravity); } /** @@ -84,7 +148,7 @@ public class UiSettings { * @return The gravity */ public int getCompassGravity() { - return compassGravity; + return compassSettings.getGravity(); } /** @@ -98,8 +162,8 @@ public class UiSettings { */ @UiThread public void setCompassMargins(int left, int top, int right, int bottom) { - this.compassMargins = new int[]{left, top, right, bottom}; - this.mapView.setCompassMargins(left, top, right, bottom); + compassSettings.setMargins(new int[]{left, top, right, bottom}); + mapView.setCompassMargins(left, top, right, bottom); } /** @@ -108,7 +172,7 @@ public class UiSettings { * @return The left margin in pixels */ public int getCompassMarginLeft() { - return compassMargins[0]; + return compassSettings.getMargins()[0]; } /** @@ -117,7 +181,7 @@ public class UiSettings { * @return The top margin in pixels */ public int getCompassMarginTop() { - return compassMargins[1]; + return compassSettings.getMargins()[1]; } /** @@ -126,7 +190,7 @@ public class UiSettings { * @return The right margin in pixels */ public int getCompassMarginRight() { - return compassMargins[2]; + return compassSettings.getMargins()[2]; } /** @@ -135,7 +199,7 @@ public class UiSettings { * @return The bottom margin in pixels */ public int getCompassMarginBottom() { - return compassMargins[3]; + return compassSettings.getMargins()[3]; } /** @@ -147,8 +211,8 @@ public class UiSettings { * @param enabled True to enable the logo; false to disable the logo. */ public void setLogoEnabled(boolean enabled) { - this.logoEnabled = enabled; - this.mapView.setLogoVisibility(enabled ); + logoSettings.setEnabled(enabled); + mapView.setLogoVisibility(enabled); } /** @@ -157,7 +221,7 @@ public class UiSettings { * @return True if the logo is enabled; false if the logo is disabled. */ public boolean isLogoEnabled() { - return logoEnabled; + return logoSettings.isEnabled(); } /** @@ -171,8 +235,8 @@ public class UiSettings { * @see Gravity */ public void setLogoGravity(int gravity) { - this.logoGravity = gravity; - this.mapView.setLogoGravity(gravity); + logoSettings.setGravity(gravity); + mapView.setLogoGravity(gravity); } /** @@ -181,7 +245,7 @@ public class UiSettings { * @return The gravity */ public int getLogoGravity() { - return logoGravity; + return logoSettings.getGravity(); } /** @@ -194,8 +258,8 @@ public class UiSettings { * @param bottom The bottom margin in pixels. */ public void setLogoMargins(int left, int top, int right, int bottom) { - this.logoMargins = new int[]{left, top, right, bottom}; - this.mapView.setLogoMargins(left, top, right, bottom); + logoSettings.setMargins(new int[]{left, top, right, bottom}); + mapView.setLogoMargins(left, top, right, bottom); } /** @@ -203,8 +267,8 @@ public class UiSettings { * * @return The left margin in pixels */ - public int getLogoMarginLeft(){ - return logoMargins[0]; + public int getLogoMarginLeft() { + return logoSettings.getMargins()[0]; } /** @@ -212,8 +276,8 @@ public class UiSettings { * * @return The top margin in pixels */ - public int getLogoMarginTop(){ - return logoMargins[1]; + public int getLogoMarginTop() { + return logoSettings.getMargins()[1]; } /** @@ -221,8 +285,8 @@ public class UiSettings { * * @return The right margin in pixels */ - public int getLogoMarginRight(){ - return logoMargins[2]; + public int getLogoMarginRight() { + return logoSettings.getMargins()[2]; } /** @@ -230,8 +294,8 @@ public class UiSettings { * * @return The bottom margin in pixels */ - public int getLogoMarginBottom(){ - return logoMargins[3]; + public int getLogoMarginBottom() { + return logoSettings.getMargins()[3]; } /** @@ -243,8 +307,8 @@ public class UiSettings { * @param enabled True to enable the logo; false to disable the logo. */ public void setAttributionEnabled(boolean enabled) { - this.attributionEnabled = enabled; - this.mapView.setAttributionVisibility(enabled ? View.VISIBLE : View.GONE); + attributionSettings.setEnabled(enabled); + mapView.setAttributionVisibility(enabled ? View.VISIBLE : View.GONE); } /** @@ -253,7 +317,7 @@ public class UiSettings { * @return True if the logo is enabled; false if the logo is disabled. */ public boolean isAttributionEnabled() { - return attributionEnabled; + return attributionSettings.isEnabled(); } /** @@ -267,8 +331,8 @@ public class UiSettings { * @see Gravity */ public void setAttributionGravity(int gravity) { - this.attributionGravity = gravity; - this.mapView.setAttributionGravity(gravity); + attributionSettings.setGravity(gravity); + mapView.setAttributionGravity(gravity); } /** @@ -277,7 +341,7 @@ public class UiSettings { * @return The gravity */ public int getAttributionGravity() { - return attributionGravity; + return attributionSettings.getGravity(); } /** @@ -290,8 +354,8 @@ public class UiSettings { * @param bottom The bottom margin in pixels. */ public void setAttributionMargins(int left, int top, int right, int bottom) { - this.attributionMargins = new int[]{left, top, right, bottom}; - this.mapView.setAttributionMargins(left, top, right, bottom); + attributionSettings.setMargins(new int[]{left, top, right, bottom}); + mapView.setAttributionMargins(left, top, right, bottom); } /** @@ -299,8 +363,8 @@ public class UiSettings { * * @return The left margin in pixels */ - public int getAttributionMarginLeft(){ - return attributionMargins[0]; + public int getAttributionMarginLeft() { + return attributionSettings.getMargins()[0]; } /** @@ -308,8 +372,8 @@ public class UiSettings { * * @return The top margin in pixels */ - public int getAttributionMarginTop(){ - return attributionMargins[1]; + public int getAttributionMarginTop() { + return attributionSettings.getMargins()[1]; } /** @@ -317,8 +381,8 @@ public class UiSettings { * * @return The right margin in pixels */ - public int getAttributionMarginRight(){ - return attributionMargins[2]; + public int getAttributionMarginRight() { + return attributionSettings.getMargins()[2]; } /** @@ -326,8 +390,8 @@ public class UiSettings { * * @return The bottom margin in pixels */ - public int getAttributionMarginBottom(){ - return attributionMargins[3]; + public int getAttributionMarginBottom() { + return attributionSettings.getMargins()[3]; } /** @@ -477,4 +541,31 @@ public class UiSettings { setTiltGesturesEnabled(enabled); setZoomGesturesEnabled(enabled); } + + /** + * Returns the measured height of the MapView + * + * @return height in pixels + */ + public float getHeight() { + return mapView.getMeasuredHeight(); + } + + /** + * Returns the measured width of the MapView + * + * @return widht in pixels + */ + public float getWidth() { + return mapView.getMeasuredWidth(); + } + + /** + * Invalidates the ViewSettings instances shown on top of the MapView + */ + public void invalidate() { + mapView.setLogoMargins(getLogoMarginLeft(), getLogoMarginTop(), getLogoMarginRight(), getLogoMarginBottom()); + mapView.setCompassMargins(getCompassMarginLeft(), getCompassMarginTop(), getCompassMarginRight(), getCompassMarginBottom()); + mapView.setAttributionMargins(getAttributionMarginLeft(), getAttributionMarginTop(), getAttributionMarginRight(), getAttributionMarginBottom()); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java new file mode 100644 index 0000000000..a192a1b576 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ViewSettings.java @@ -0,0 +1,36 @@ +package com.mapbox.mapboxsdk.maps; + +public class ViewSettings { + + private boolean enabled; + private int gravity; + private int[]margins; + + public ViewSettings() { + margins = new int[4]; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getGravity() { + return gravity; + } + + public void setGravity(int gravity) { + this.gravity = gravity; + } + + public int[] getMargins() { + return margins; + } + + public void setMargins(int[] margins) { + this.margins = margins; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 0d84289332..28afb70de3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -1,4 +1,4 @@ -package com.mapbox.mapboxsdk.maps; +package com.mapbox.mapboxsdk.maps.widgets; import android.content.Context; import android.support.v4.content.ContextCompat; @@ -11,6 +11,8 @@ import android.view.ViewGroup; import android.widget.ImageView; import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; import java.lang.ref.WeakReference; import java.util.Timer; @@ -21,7 +23,7 @@ import java.util.TimerTask; * when it isn't true north (0.0). Tapping the compass resets the bearing to true * north and hides the compass. */ -public class CompassView extends ImageView { +public final class CompassView extends ImageView { private Timer mNorthTimer; private double mDirection = 0.0f; @@ -46,7 +48,7 @@ public class CompassView extends ImageView { // View configuration setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass)); - setContentDescription(getResources().getString(R.string.compassContentDescription)); + setContentDescription(getResources().getString(R.string.mapbox_compassContentDescription)); setEnabled(false); // Layout params @@ -139,17 +141,17 @@ public class CompassView extends ImageView { public static class CompassClickListener implements View.OnClickListener { - private WeakReference<MapView> mMapView; + private WeakReference<MapboxMap> mMapboxMap; - public CompassClickListener(final MapView mapView) { - mMapView = new WeakReference<>(mapView); + public CompassClickListener(final MapboxMap mapboxMap) { + mMapboxMap = new WeakReference<>(mapboxMap); } @Override public void onClick(View v) { - final MapView mapView = mMapView.get(); - if (mapView != null) { - mapView.resetNorth(); + final MapboxMap mapboxMap = mMapboxMap.get(); + if (mapboxMap != null) { + mapboxMap.resetNorth(); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UserLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java index 9f8261a0a7..98d66b9307 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UserLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java @@ -1,4 +1,4 @@ -package com.mapbox.mapboxsdk.maps; +package com.mapbox.mapboxsdk.maps.widgets; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; @@ -27,11 +27,14 @@ import android.view.ViewGroup; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MyBearingTracking; import com.mapbox.mapboxsdk.constants.MyLocationTracking; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.location.LocationListener; -import com.mapbox.mapboxsdk.location.LocationServices; +import com.mapbox.mapboxsdk.location.LocationService; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; import java.lang.ref.WeakReference; @@ -42,9 +45,8 @@ import java.lang.ref.WeakReference; public final class UserLocationView extends View { - private MapView mMapView; - - private float mDensity; + private MapboxMap mMapboxMap; + private Projection mProjection; private boolean mShowMarker; private boolean mShowDirection; @@ -84,7 +86,6 @@ public final class UserLocationView extends View { private LatLng mCurrentMapViewCoordinate; private double mCurrentBearing; - private boolean mPaused = false; private Location mUserLocation; private UserLocationListener mUserLocationListener; @@ -99,7 +100,6 @@ public final class UserLocationView extends View { // Compass data private MyBearingListener mBearingChangeListener; - private static final long BEARING_DURATION = 100; public UserLocationView(Context context) { super(context); @@ -132,9 +132,9 @@ public final class UserLocationView extends View { // Setup the custom paint Resources resources = context.getResources(); - int accuracyColor = resources.getColor(R.color.my_location_ring); + int accuracyColor = ContextCompat.getColor(context,R.color.my_location_ring); - mDensity = resources.getDisplayMetrics().density; + float density = resources.getDisplayMetrics().density; mMarkerCoordinate = new LatLng(0.0, 0.0); mMarkerScreenPoint = new PointF(); mMarkerScreenMatrix = new Matrix(); @@ -148,7 +148,7 @@ public final class UserLocationView extends View { mAccuracyPaintStroke = new Paint(); mAccuracyPaintStroke.setAntiAlias(true); mAccuracyPaintStroke.setStyle(Paint.Style.STROKE); - mAccuracyPaintStroke.setStrokeWidth(0.5f * mDensity); + mAccuracyPaintStroke.setStrokeWidth(0.5f * density); mAccuracyPaintStroke.setColor(accuracyColor); mAccuracyPaintStroke.setAlpha((int) (255 * 0.5f)); @@ -195,8 +195,9 @@ public final class UserLocationView extends View { mUserLocationStaleDrawable.setBounds(mUserLocationStaleDrawableBounds); } - public void setMapView(MapView mapView) { - mMapView = mapView; + public void setMapboxMap(MapboxMap mapboxMap) { + mMapboxMap = mapboxMap; + mProjection = mapboxMap.getProjection(); } public void onStart() { @@ -247,21 +248,15 @@ public final class UserLocationView extends View { if (myLocationTrackingMode != MyLocationTracking.TRACKING_NONE && mUserLocation != null) { // center map directly if we have a location fix mMarkerCoordinate = new LatLng(mUserLocation.getLatitude(), mUserLocation.getLongitude()); - mMapView.getMapboxMap().moveCamera(CameraUpdateFactory.newLatLng(new LatLng(mUserLocation))); + mMapboxMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(mUserLocation))); // center view directly mMarkerScreenMatrix.reset(); - mMarkerScreenMatrix.setTranslate( - getMeasuredWidth() / 2, - getMeasuredHeight() / 2); + mMarkerScreenPoint = getMarkerScreenPoint(); + mMarkerScreenMatrix.setTranslate(mMarkerScreenPoint.x, mMarkerScreenPoint.y); } } - @MyLocationTracking.Mode - public int getMyLocationTrackingMode() { - return mMyLocationTrackingMode; - } - @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -278,7 +273,7 @@ public final class UserLocationView extends View { // compute new marker position // TODO add JNI method that takes existing pointf if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { - mMarkerScreenPoint = mMapView.toScreenLocation(mMarkerCoordinate); + mMarkerScreenPoint = getMarkerScreenPoint(); mMarkerScreenMatrix.reset(); mMarkerScreenMatrix.setTranslate( mMarkerScreenPoint.x, @@ -289,11 +284,11 @@ public final class UserLocationView extends View { if (mShowDirection) { bearing = mMyBearingTrackingMode == MyBearingTracking.COMPASS ? mBearingChangeListener.getCompassBearing() : mUserLocation.getBearing(); } else { - bearing = (float) mMapView.getBearing(); + bearing = mMapboxMap.getCameraPosition().bearing; } if (mCurrentMapViewCoordinate == null) { - mCurrentMapViewCoordinate = mMapView.getMapboxMap().getCameraPosition().target; + mCurrentMapViewCoordinate = mMapboxMap.getCameraPosition().target; } // only update if there is an actual change @@ -302,11 +297,10 @@ public final class UserLocationView extends View { .target(mMarkerCoordinate) .bearing(bearing) .build(); - mMapView.getMapboxMap().animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 300, null); + mMapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 300, null); mMarkerScreenMatrix.reset(); - mMarkerScreenMatrix.setTranslate( - getMeasuredWidth() / 2, - getMeasuredHeight() / 2); + mMarkerScreenPoint = getMarkerScreenPoint(); + mMarkerScreenMatrix.setTranslate(mMarkerScreenPoint.x, mMarkerScreenPoint.y); // set values for next check for actual change mCurrentMapViewCoordinate = mMarkerCoordinate; @@ -317,10 +311,10 @@ public final class UserLocationView extends View { // rotate so arrow in points to bearing if (mShowDirection) { if (mMyBearingTrackingMode == MyBearingTracking.COMPASS && mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { - mMarkerScreenMatrix.preRotate(mCompassMarkerDirection + (float) mMapView.getDirection()); + mMarkerScreenMatrix.preRotate(mCompassMarkerDirection + mMapboxMap.getCameraPosition().bearing); } else if (mMyBearingTrackingMode == MyBearingTracking.GPS) { if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { - mMarkerScreenMatrix.preRotate(mGpsMarkerDirection + (float) mMapView.getDirection()); + mMarkerScreenMatrix.preRotate(mGpsMarkerDirection + mMapboxMap.getCameraPosition().bearing); } else { mMarkerScreenMatrix.preRotate(mGpsMarkerDirection); } @@ -331,7 +325,7 @@ public final class UserLocationView extends View { if (mShowAccuracy && !mStaleMarker) { mAccuracyPath.reset(); mAccuracyPath.addCircle(0.0f, 0.0f, - (float) (mMarkerAccuracy / mMapView.getMetersPerPixelAtLatitude( + (float) (mMarkerAccuracy / mMapboxMap.getProjection().getMetersPerPixelAtLatitude( mMarkerCoordinate.getLatitude())), Path.Direction.CW); @@ -371,11 +365,11 @@ public final class UserLocationView extends View { */ private void toggleGps(boolean enableGps) { - LocationServices locationServices = LocationServices.getLocationServices(getContext()); + LocationService locationService = LocationService.getInstance(getContext()); if (enableGps) { // Set an initial location if one available - Location lastLocation = locationServices.getLastLocation(); + Location lastLocation = locationService.getLastLocation(); if (lastLocation != null) { setLocation(lastLocation); } @@ -385,16 +379,16 @@ public final class UserLocationView extends View { } // Register for Location Updates - locationServices.addLocationListener(mUserLocationListener); + locationService.addLocationListener(mUserLocationListener); } else { // Disable location and user dot setLocation(null); // Deregister for Location Updates - locationServices.removeLocationListener(mUserLocationListener); + locationService.removeLocationListener(mUserLocationListener); } - locationServices.toggleGPS(enableGps); + locationService.toggleGPS(enableGps); } public void setMyBearingTrackingMode(@MyBearingTracking.Mode int myBearingTrackingMode) { @@ -415,11 +409,6 @@ public final class UserLocationView extends View { update(); } - @MyBearingTracking.Mode - public int getMyBearingTrackingMode() { - return mMyBearingTrackingMode; - } - private class MyBearingListener implements SensorEventListener { private SensorManager mSensorManager; @@ -483,9 +472,8 @@ public final class UserLocationView extends View { SensorManager.getRotationMatrix(mR, null, mLastAccelerometer, mLastMagnetometer); SensorManager.getOrientation(mR, mOrientation); float azimuthInRadians = mOrientation[0]; - float azimuthInDegress = (float) (Math.toDegrees(azimuthInRadians) + 360) % 360; - mCompassBearing = azimuthInDegress; + mCompassBearing = (float) (Math.toDegrees(azimuthInRadians) + 360) % 360; if (mCompassBearing < 0) { // only allow positive degrees mCompassBearing += 360; @@ -515,7 +503,7 @@ public final class UserLocationView extends View { /** - * Callback method for receiving location updates from LocationServices. + * Callback method for receiving location updates from LocationService. * * @param location The new Location data */ @@ -670,7 +658,7 @@ public final class UserLocationView extends View { } void updateOnNextFrame() { - mMapView.update(); + mMapboxMap.invalidate(); } /** @@ -770,4 +758,14 @@ public final class UserLocationView extends View { return mPaused; } + public PointF getMarkerScreenPoint() { + if (mMyLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { + mMarkerScreenPoint = mProjection.toScreenLocation(mMarkerCoordinate); + } else { + int[] contentPadding = mMapboxMap.getPadding(); + mMarkerScreenPoint = new PointF(((getMeasuredWidth() + contentPadding[0] - contentPadding[2]) / 2) + , ((getMeasuredHeight() - contentPadding[3] + contentPadding[1]) / 2)); + } + return mMarkerScreenPoint; + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java new file mode 100644 index 0000000000..87dfb7ec3c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEvent.java @@ -0,0 +1,59 @@ +package com.mapbox.mapboxsdk.telemetry; + +import java.io.Serializable; + +public class MapboxEvent implements Serializable { + public static final int VERSION_NUMBER = 1; + public static final String MGLMapboxEventsUserAgent = "MapboxEventsAndroid/1.1"; + public static final String MAPBOX_EVENTS_BASE_URL = "https://api.mapbox.com"; + + // Event Types + public static final String TYPE_TURNSTILE = "appUserTurnstile"; + public static final String TYPE_MAP_LOAD = "map.load"; + public static final String TYPE_MAP_CLICK = "map.click"; + public static final String TYPE_MAP_DRAGEND = "map.dragend"; + public static final String TYPE_LOCATION = "location"; + public static final String TYPE_VISIT = "visit"; + + // Event Keys + public static final String KEY_LATITUDE = "lat"; + public static final String KEY_LONGITUDE = "lng"; + public static final String KEY_SPEED = "speed"; + public static final String KEY_COURSE = "course"; + public static final String KEY_ALTITUDE = "altitude"; + public static final String KEY_HORIZONTAL_ACCURACY = "horizontalAccuracy"; + public static final String KEY_ZOOM = "zoom"; + + public static final String KEY_PUSH_ENABLED = "enabled.push"; + public static final String KEY_EMAIL_ENABLED = "enabled.email"; + public static final String KEY_GESTURE_ID = "gesture"; + public static final String KEY_ARRIVAL_DATE = "arrivalDate"; + public static final String KEY_DEPARTURE_DATE = "departureDate"; + + public static final String GESTURE_SINGLETAP = "SingleTap"; + public static final String GESTURE_DOUBLETAP = "DoubleTap"; + public static final String GESTURE_TWO_FINGER_SINGLETAP = "TwoFingerTap"; + public static final String GESTURE_QUICK_ZOOM = "QuickZoom"; + public static final String GESTURE_PAN_START = "Pan"; + public static final String GESTURE_PINCH_START = "Pinch"; + public static final String GESTURE_ROTATION_START = "Rotation"; + public static final String GESTURE_PITCH_START = "Pitch"; + + // Event Attributes + public static final String ATTRIBUTE_EVENT = "event"; + public static final String ATTRIBUTE_SESSION_ID = "sessionId"; + public static final String ATTRIBUTE_VERSION = "version"; + public static final String ATTRIBUTE_CREATED = "created"; + public static final String ATTRIBUTE_VENDOR_ID = "vendorId"; + public static final String ATTRIBUTE_APP_BUNDLE_ID = "appBundleId"; + public static final String ATTRIBUTE_MODEL = "model"; + public static final String ATTRIBUTE_OPERATING_SYSTEM= "operatingSystem"; + public static final String ATTRIBUTE_ORIENTATION = "orientation"; + public static final String ATTRIBUTE_BATTERY_LEVEL = "batteryLevel"; + public static final String ATTRIBUTE_APPLICATION_STATE = "applicationState"; + public static final String ATTRIBUTE_RESOLUTION = "resolution"; + public static final String ATTRIBUTE_ACCESSIBILITY_FONT_SCALE = "accessibilityFontScale"; + public static final String ATTRIBUTE_CARRIER = "carrier"; + public static final String ATTRIBUTE_CELLULAR_NETWORK_TYPE = "cellularNetworkType"; + public static final String ATTRIBUTE_WIFI = "wifi"; +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java new file mode 100644 index 0000000000..a62fdc98c5 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java @@ -0,0 +1,532 @@ +package com.mapbox.mapboxsdk.telemetry; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.location.Location; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.AsyncTask; +import android.os.BatteryManager; +import android.os.Build; +import android.support.annotation.NonNull; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; +import com.mapbox.mapboxsdk.constants.MapboxConstants; +import com.mapbox.mapboxsdk.location.LocationService; +import com.mapbox.mapboxsdk.utils.ApiAccess; +import org.json.JSONArray; +import org.json.JSONObject; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.Vector; +import okhttp3.CertificatePinner; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class MapboxEventManager { + + private static final String TAG = "MapboxEventManager"; + + private static MapboxEventManager mapboxEventManager = null; + + private boolean telemetryEnabled; + + private final Vector<Hashtable<String, Object>> events = new Vector<>(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + + private Context context = null; + private String accessToken = null; + private String eventsURL = MapboxEvent.MAPBOX_EVENTS_BASE_URL; + + private String userAgent = MapboxEvent.MGLMapboxEventsUserAgent; + + private Intent batteryStatus = null; + + private DisplayMetrics displayMetrics = null; + + private String mapboxVendorId = null; + + private String mapboxSessionId = null; + private long mapboxSessionIdLastSet = 0; + private static long hourInMillis = 1000 * 60 * 60; + private static long flushDelayInitialInMillis = 1000 * 10; // 10 Seconds + private static long flushDelayInMillis = 1000 * 60 * 2; // 2 Minutes + private static final int SESSION_ID_ROTATION_HOURS = 24; + + private static MessageDigest messageDigest = null; + + private Timer timer = null; + + private MapboxEventManager(@NonNull Context context) { + super(); + this.accessToken = ApiAccess.getToken(context); + this.context = context; + + // Setup Message Digest + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, "Error getting Encryption Algorithm: " + e); + } + + SharedPreferences prefs = context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE); + + // Determine if Telemetry Should Be Enabled + setTelemetryEnabled(prefs.getBoolean(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, true)); + + // Load / Create Vendor Id + if (prefs.contains(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID)) { + mapboxVendorId = prefs.getString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, "Default Value"); + Log.d(TAG, "Found Vendor Id = " + mapboxVendorId); + } else { + String vendorId = UUID.randomUUID().toString(); + vendorId = encodeString(vendorId); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_VENDORID, vendorId); + editor.apply(); + editor.commit(); + Log.d(TAG, "Set New Vendor Id = " + vendorId); + } + + // Create Initial Session Id + rotateSessionId(); + + // Get DisplayMetrics Setup + displayMetrics = new DisplayMetrics(); + ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics); + + // Check for Staging Server Information + try { + ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + String stagingURL = appInfo.metaData.getString(MapboxConstants.KEY_META_DATA_STAGING_SERVER); + String stagingAccessToken = appInfo.metaData.getString(MapboxConstants.KEY_META_DATA_STAGING_ACCESS_TOKEN); + String appName = context.getPackageManager().getApplicationLabel(appInfo).toString(); + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + String versionName = packageInfo.versionName; + int versionCode = packageInfo.versionCode; + + if (!TextUtils.isEmpty(stagingURL)) { + eventsURL = stagingURL; + } + + if (!TextUtils.isEmpty(stagingAccessToken)) { + this.accessToken = stagingAccessToken; + } + + // Build User Agent + if (!TextUtils.isEmpty(appName) && !TextUtils.isEmpty(versionName)) { + userAgent = appName + "/" + versionName + "/" + versionCode + " " + userAgent; + } + + } catch (Exception e) { + Log.e(TAG, "Error Trying to load Staging Credentials: " + e.toString()); + } + + // Register for battery updates + IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + batteryStatus = context.registerReceiver(null, iFilter); + } + + /** + * Primary Access method using Singleton pattern + * @param context Application Context + * @return MapboxEventManager + */ + public static MapboxEventManager getMapboxEventManager(@NonNull Context context) { + if (mapboxEventManager == null) { + mapboxEventManager = new MapboxEventManager(context.getApplicationContext()); + } + return mapboxEventManager; + } + + public boolean isTelemetryEnabled() { + return telemetryEnabled; + } + + /** + * Enables / Disables Telemetry + * @param telemetryEnabled True to start telemetry, false to stop it + */ + public void setTelemetryEnabled(boolean telemetryEnabled) { + if (this.telemetryEnabled == telemetryEnabled) { + Log.i(TAG, "no need to start / stop telemetry as it's already in that state."); + return; + } + + if (telemetryEnabled) { + Log.i(TAG, "Starting Telemetry Up!"); + // Start It Up + context.startService(new Intent(context, TelemetryService.class)); + + // Make sure Ambient Mode is started at a minimum + if (LocationService.getInstance(context).isGPSEnabled()) { + LocationService.getInstance(context).toggleGPS(false); + } + + // Manage Timer Flush + timer = new Timer(); + timer.schedule(new FlushEventsTimerTask(), flushDelayInitialInMillis, flushDelayInMillis); + } else { + Log.i(TAG, "Shutting Telemetry Down"); + // Shut It Down + events.removeAllElements(); + context.stopService(new Intent(context, TelemetryService.class)); + + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + // Persist + this.telemetryEnabled = telemetryEnabled; + SharedPreferences prefs = context.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_ENABLED, telemetryEnabled); + editor.apply(); + editor.commit(); + } + + /** + * Adds a Location Event to the system for processing + * @param location Location event + */ + public void addLocationEvent(Location location) { + // Add Location even to queue + Hashtable<String, Object> event = new Hashtable<>(); + event.put(MapboxEvent.KEY_LATITUDE, location.getLatitude()); + event.put(MapboxEvent.KEY_LONGITUDE, location.getLongitude()); + event.put(MapboxEvent.KEY_SPEED, location.getSpeed()); + event.put(MapboxEvent.KEY_COURSE, location.getBearing()); + event.put(MapboxEvent.KEY_ALTITUDE, location.getAltitude()); + event.put(MapboxEvent.KEY_HORIZONTAL_ACCURACY, location.getAccuracy()); + event.put(MapboxEvent.ATTRIBUTE_CREATED, dateFormat.format(new Date())); + event.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_LOCATION); + + events.add(event); + + rotateSessionId(); + } + + /** + * Push Interactive Events to the system for processing + * @param eventWithAttributes Event with attributes + */ + public void pushEvent(Hashtable<String, Object> eventWithAttributes) { + + if (eventWithAttributes == null) { + return; + } + + String eventType = (String)eventWithAttributes.get(MapboxEvent.ATTRIBUTE_EVENT); + if (!TextUtils.isEmpty(eventType) && eventType.equalsIgnoreCase(MapboxEvent.TYPE_MAP_LOAD)) { + pushTurnstileEvent(); + } + + events.add(eventWithAttributes); + } + + /** + * Pushes turnstile event for internal billing purposes + */ + private void pushTurnstileEvent() { + + Hashtable<String, Object> event = new Hashtable<>(); + event.put(MapboxEvent.ATTRIBUTE_EVENT, MapboxEvent.TYPE_TURNSTILE); + event.put(MapboxEvent.ATTRIBUTE_CREATED, dateFormat.format(new Date())); +/* + // Already set by processing + event.put(MapboxEvent.ATTRIBUTE_APP_BUNDLE_ID, context.getPackageName()); + event.put(MapboxEvent.ATTRIBUTE_VERSION, MapboxEvent.VERSION_NUMBER); + event.put(MapboxEvent.ATTRIBUTE_VENDOR_ID, mapboxVendorId); +*/ + + events.add(event); + + // Send to Server Immediately + new FlushTheEventsTask().execute(); + Log.d(TAG, "turnstile event pushed."); + } + + /** + * SHA-1 Encoding for strings + * @param string String to encode + * @return String encoded if no error, original string if error + */ + private String encodeString(String string) { + try { + if (messageDigest != null) { + messageDigest.reset(); + messageDigest.update(string.getBytes("UTF-8")); + byte[] bytes = messageDigest.digest(); + + // Get the Hex version of the digest + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append( String.format("%02X", b) ); + } + String hex = sb.toString(); + Log.d(TAG, "original = " + string + "; hex = " + hex); + + return hex; + } + } catch (Exception e) { + Log.w(TAG, "Error encoding string, will return in original form." + e); + } + return string; + } + + /** + * Changes Session Id based on time boundary + */ + private void rotateSessionId() { + long now = System.currentTimeMillis(); + if (now - mapboxSessionIdLastSet > (SESSION_ID_ROTATION_HOURS * hourInMillis)) { + mapboxSessionId = UUID.randomUUID().toString(); + mapboxSessionIdLastSet = System.currentTimeMillis(); + } + } + + private String getOrientation() { + switch (context.getResources().getConfiguration().orientation) { + case Configuration.ORIENTATION_LANDSCAPE: + return "Landscape"; + case Configuration.ORIENTATION_PORTRAIT: + return "Portrait"; + default: + return "Undefined"; + } + } + + private int getBatteryLevel() { + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + return Math.round((level / (float)scale) * 100); + } + + private String getApplicationState() { + + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return "Unknown"; + } + final String packageName = context.getPackageName(); + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) { + return "Foreground"; + } + } + return "Background"; + } + + private float getAccesibilityFontScaleSize() { + // Values + // Small = 0.85 + // Normal = 1.0 + // Large = 1.15 + // Huge = 1.3 + + return context.getResources().getConfiguration().fontScale; + } + + private String getCellularCarrier() { + TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + String carrierName = manager.getNetworkOperatorName(); + if (TextUtils.isEmpty(carrierName)) { + carrierName = "None"; + } + return carrierName; + } + + private String getCellularNetworkType () { + TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + switch (manager.getNetworkType()) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + return "1xRTT"; + case TelephonyManager.NETWORK_TYPE_CDMA: + return "CDMA"; + case TelephonyManager.NETWORK_TYPE_EDGE: + return "EDGE"; + case TelephonyManager.NETWORK_TYPE_EHRPD: + return "EHRPD"; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + return "EVDO_0"; + case TelephonyManager.NETWORK_TYPE_EVDO_A: + return "EVDO_A"; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + return "EVDO_B"; + case TelephonyManager.NETWORK_TYPE_GPRS: + return "GPRS"; + case TelephonyManager.NETWORK_TYPE_HSDPA: + return "HSDPA"; + case TelephonyManager.NETWORK_TYPE_HSPA: + return "HSPA"; + case TelephonyManager.NETWORK_TYPE_HSPAP: + return "HSPAP"; + case TelephonyManager.NETWORK_TYPE_HSUPA: + return "HSUPA"; + case TelephonyManager.NETWORK_TYPE_IDEN: + return "IDEN"; + case TelephonyManager.NETWORK_TYPE_LTE: + return "LTE"; + case TelephonyManager.NETWORK_TYPE_UMTS: + return "UMTS"; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + return "Unknown"; + default: + return "Default Unknown"; + } + } + + + public String getConnectedToWifi() { + + String status = "No"; + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiMgr.isWifiEnabled()) { + try { + WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); + if( wifiInfo.getNetworkId() != -1 ){ + status = "Yes"; + } + } catch (Exception e) { + Log.w(TAG, "Error getting Wifi Connection Status: " + e); + status = "Unknown"; + } + } + + return status; + } + + /** + * Task responsible for converting stored events and sending them to the server + */ + private class FlushTheEventsTask extends AsyncTask<Void, Void, Void> { + + @Override + protected Void doInBackground(Void... voids) { + + if (events.size() < 1) { + Log.i(TAG, "No events in the queue to send so returning."); + return null; + } + + // Check for NetworkConnectivity + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected()) { + Log.w(TAG, "Not connected to network, so returning without attempting to send events"); + return null; + } + + try { + // Send data + // ========= + JSONArray jsonArray = new JSONArray(); + + for (Hashtable<String, Object> evt : events) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(MapboxEvent.KEY_LATITUDE, evt.get(MapboxEvent.KEY_LATITUDE)); + jsonObject.put(MapboxEvent.KEY_LONGITUDE, evt.get(MapboxEvent.KEY_LONGITUDE)); + jsonObject.put(MapboxEvent.KEY_SPEED, evt.get(MapboxEvent.KEY_SPEED)); + jsonObject.put(MapboxEvent.KEY_COURSE, evt.get(MapboxEvent.KEY_COURSE)); + jsonObject.put(MapboxEvent.KEY_ALTITUDE, evt.get(MapboxEvent.KEY_ALTITUDE)); + jsonObject.put(MapboxEvent.KEY_HORIZONTAL_ACCURACY, evt.get(MapboxEvent.KEY_HORIZONTAL_ACCURACY)); + jsonObject.put(MapboxEvent.KEY_ZOOM, evt.get(MapboxEvent.KEY_ZOOM)); + + // Basic Event Meta Data + jsonObject.put(MapboxEvent.ATTRIBUTE_EVENT, evt.get(MapboxEvent.ATTRIBUTE_EVENT)); + jsonObject.put(MapboxEvent.ATTRIBUTE_CREATED, evt.get(MapboxEvent.ATTRIBUTE_CREATED)); + jsonObject.put(MapboxEvent.ATTRIBUTE_SESSION_ID, encodeString(mapboxSessionId)); + jsonObject.put(MapboxEvent.ATTRIBUTE_VERSION, MapboxEvent.VERSION_NUMBER); + jsonObject.put(MapboxEvent.ATTRIBUTE_VENDOR_ID, mapboxVendorId); + jsonObject.put(MapboxEvent.ATTRIBUTE_APP_BUNDLE_ID, context.getPackageName()); + jsonObject.put(MapboxEvent.ATTRIBUTE_MODEL, Build.MODEL); + jsonObject.put(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM, Build.VERSION.RELEASE); + jsonObject.put(MapboxEvent.ATTRIBUTE_ORIENTATION, getOrientation()); + jsonObject.put(MapboxEvent.ATTRIBUTE_BATTERY_LEVEL, getBatteryLevel()); + jsonObject.put(MapboxEvent.ATTRIBUTE_APPLICATION_STATE, getApplicationState()); + jsonObject.put(MapboxEvent.ATTRIBUTE_RESOLUTION, displayMetrics.density); + jsonObject.put(MapboxEvent.ATTRIBUTE_ACCESSIBILITY_FONT_SCALE, getAccesibilityFontScaleSize()); + jsonObject.put(MapboxEvent.ATTRIBUTE_CARRIER, getCellularCarrier()); + jsonObject.put(MapboxEvent.ATTRIBUTE_CELLULAR_NETWORK_TYPE, getCellularNetworkType()); + jsonObject.put(MapboxEvent.ATTRIBUTE_WIFI, getConnectedToWifi()); + + jsonArray.put(jsonObject); + } + + // Based on http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html + CertificatePinner certificatePinner = new CertificatePinner.Builder() + .add("cloudfront-staging.tilestream.net", "sha1/KcdiTca54HxWTV8VuAd67x8I=") + .add("cloudfront-staging.tilestream.net", "sha1//KDE76PP0DQBDcTnMFBv+efp4eg=") + .add("api.mapbox.com", "sha1/Uv71ooi32pyba+oLD7egnXm7/GQ=") + .add("api.mapbox.com", "sha1/hOP0d37/ZTSGgCSseE3DIZ1uSg0=") + .build(); + + OkHttpClient client = new OkHttpClient.Builder().certificatePinner(certificatePinner).build(); + RequestBody body = RequestBody.create(JSON, jsonArray.toString()); + + String url = eventsURL + "/events/v1?access_token=" + accessToken; + Log.d(TAG, "url = " + url); + + Request request = new Request.Builder() + .url(url) + .header("User-Agent", userAgent) + .post(body) + .build(); + Response response = client.newCall(request).execute(); + Log.d(TAG, "Response Code from Mapbox Events Server: " + response.code() + " for " + events.size() + " events sent in."); + + // Reset Events + // ============ + events.removeAllElements(); + } catch (Exception e) { + Log.e(TAG, "FlushTheEventsTask borked: " + e); + } + + return null; + } + + } + + + /** + * TimerTask responsible for sending event data to server + */ + private class FlushEventsTimerTask extends TimerTask { + /** + * The task to run should be specified in the implementation of the {@code run()} + * method. + */ + @Override + public void run() { + new FlushTheEventsTask().execute(); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryLocationReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryLocationReceiver.java new file mode 100644 index 0000000000..088d41be54 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryLocationReceiver.java @@ -0,0 +1,71 @@ +package com.mapbox.mapboxsdk.telemetry; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.location.LocationManager; +import android.media.AudioManager; +import android.media.ToneGenerator; +import android.os.Handler; +import android.util.Log; + +public class TelemetryLocationReceiver extends BroadcastReceiver { + + private static final String TAG = "TelemLocationReceiver"; + + public static final String INTENT_STRING = "com.mapbox.mapboxsdk.telemetry.TelemetryLocationReceiver"; + + /** + * Default Constructor + */ + public TelemetryLocationReceiver() { + super(); + } + + /** + * This method is called when the BroadcastReceiver is receiving an Intent + * broadcast. During this time you can use the other methods on + * BroadcastReceiver to view/modify the current result values. This method + * is always called within the main thread of its process, unless you + * explicitly asked for it to be scheduled on a different thread using + * {@link Context#registerReceiver(BroadcastReceiver, + * IntentFilter, String, Handler)}. When it runs on the main + * thread you should + * never perform long-running operations in it (there is a timeout of + * 10 seconds that the system allows before considering the receiver to + * be blocked and a candidate to be killed). You cannot launch a popup dialog + * in your implementation of onReceive(). + * <p/> + * <p><b>If this BroadcastReceiver was launched through a <receiver> tag, + * then the object is no longer alive after returning from this + * function.</b> This means you should not perform any operations that + * return a result to you asynchronously -- in particular, for interacting + * with services, you should use + * {@link Context#startService(Intent)} instead of + * {@link Context#bindService(Intent, ServiceConnection, int)}. If you wish + * to interact with a service that is already running, you can use + * {@link #peekService}. + * <p/> + * <p>The Intent filters used in {@link Context#registerReceiver} + * and in application manifests are <em>not</em> guaranteed to be exclusive. They + * are hints to the operating system about how to find suitable recipients. It is + * possible for senders to force delivery to specific recipients, bypassing filter + * resolution. For this reason, {@link #onReceive(Context, Intent) onReceive()} + * implementations should respond only to known actions, ignoring any unexpected + * Intents that they may receive. + * + * @param context The Context in which the receiver is running. + * @param intent The Intent being received. + */ + @Override + public void onReceive(Context context, Intent intent) { + Location location = (Location)intent.getExtras().get(LocationManager.KEY_LOCATION_CHANGED); + if (location != null) { + Log.d(TAG, "location received = " + location); + MapboxEventManager.getMapboxEventManager(context).addLocationEvent(location); + } else { + Log.d(TAG, "location NOT received"); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryService.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryService.java new file mode 100644 index 0000000000..56006dadf6 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/TelemetryService.java @@ -0,0 +1,145 @@ +package com.mapbox.mapboxsdk.telemetry; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ServiceInfo; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.PowerManager; +import android.support.annotation.Nullable; +import android.util.Log; + +public class TelemetryService extends Service { + + private static final String TAG = "TelemetryService"; + + private TelemetryLocationReceiver telemetryLocationReceiver = null; + private PowerManager.WakeLock telemetryWakeLock; + + /** + * Return the communication channel to the service. May return null if + * clients can not bind to the service. The returned + * {@link IBinder} is usually for a complex interface + * that has been <a href="{@docRoot}guide/components/aidl.html">described using + * aidl</a>. + * <p/> + * <p><em>Note that unlike other application components, calls on to the + * IBinder interface returned here may not happen on the main thread + * of the process</em>. More information about the main thread can be found in + * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and + * Threads</a>.</p> + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will <em>not</em> be seen here. + * @return Return an IBinder through which clients can call on to the + * service. + */ + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + + /** + * Called by the system when the service is first created. Do not call this method directly. + */ + @Override + public void onCreate() { + super.onCreate(); + + Log.i(TAG, "onCreate() called"); + + // Enable Location Listening for lifecycle of app + IntentFilter filter = new IntentFilter(TelemetryLocationReceiver.INTENT_STRING); + telemetryLocationReceiver = new TelemetryLocationReceiver(); + registerReceiver(telemetryLocationReceiver, filter); + } + + /** + * Called by the system to notify a Service that it is no longer used and is being removed. The + * service should clean up any resources it holds (threads, registered + * receivers, etc) at this point. Upon return, there will be no more calls + * in to this Service object and it is effectively dead. Do not call this method directly. + */ + @Override + public void onDestroy() { + shutdownTelemetry(); + super.onDestroy(); + } + + /** + * This is called if the service is currently running and the user has + * removed a task that comes from the service's application. If you have + * set {@link ServiceInfo#FLAG_STOP_WITH_TASK ServiceInfo.FLAG_STOP_WITH_TASK} + * then you will not receive this callback; instead, the service will simply + * be stopped. + * + * @param rootIntent The original root Intent that was used to launch + * the task that is being removed. + */ + @Override + public void onTaskRemoved(Intent rootIntent) { + shutdownTelemetry(); + super.onTaskRemoved(rootIntent); + } + + /** + * Called by the system every time a client explicitly starts the service by calling + * {@link Context#startService}, providing the arguments it supplied and a + * unique integer token representing the start request. Do not call this method directly. + * <p/> + * <p>For backwards compatibility, the default implementation calls + * {@link #onStart} and returns either {@link #START_STICKY} + * or {@link #START_STICKY_COMPATIBILITY}. + * <p/> + * <p>If you need your application to run on platform versions prior to API + * level 5, you can use the following model to handle the older {@link #onStart} + * callback in that case. The <code>handleCommand</code> method is implemented by + * you as appropriate: + * <p/> + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java + * start_compatibility} + * <p/> + * <p class="caution">Note that the system calls this on your + * service's main thread. A service's main thread is the same + * thread where UI operations take place for Activities running in the + * same process. You should always avoid stalling the main + * thread's event loop. When doing long-running operations, + * network calls, or heavy disk I/O, you should kick off a new + * thread, or use {@link AsyncTask}.</p> + * + * @param intent The Intent supplied to {@link Context#startService}, + * as given. This may be null if the service is being restarted after + * its process has gone away, and it had previously returned anything + * except {@link #START_STICKY_COMPATIBILITY}. + * @param flags Additional data about this start request. Currently either + * 0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}. + * @param startId A unique integer representing this specific request to + * start. Use with {@link #stopSelfResult(int)}. + * @return The return value indicates what semantics the system should + * use for the service's current started state. It may be one of the + * constants associated with the {@link #START_CONTINUATION_MASK} bits. + * @see #stopSelfResult(int) + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + Log.i(TAG, "onStartCommand() called"); + + // Start WakeLock to keep Location Data working when device sleeps + PowerManager mgr = (PowerManager)getSystemService(Context.POWER_SERVICE); + telemetryWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TelemetryWakeLock"); + telemetryWakeLock.acquire(); + + return START_NOT_STICKY; + } + + private void shutdownTelemetry() { + unregisterReceiver(telemetryLocationReceiver); + telemetryWakeLock.release(); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/package-info.java new file mode 100644 index 0000000000..d6cb1ca852 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains the classes that manage the SDK's Telemetry services. + */ +package com.mapbox.mapboxsdk.telemetry; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java index 137d0730c3..4f968c3a13 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ApiAccess.java @@ -37,7 +37,7 @@ public final class ApiAccess { return token; } catch (Exception e) { // use fallback on string resource, used for development - int tokenResId = context.getResources().getIdentifier("access_token", "string", context.getPackageName()); + int tokenResId = context.getResources().getIdentifier("mapbox_access_token", "string", context.getPackageName()); return tokenResId != 0 ? context.getString(tokenResId) : null; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index 777d879d48..be0038e8b8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -3,12 +3,12 @@ <!--Add references to exposed resources--> <public name="AttributionAlertDialogStyle" type="style" /> - <public name="style_mapbox_streets" type="string" /> - <public name="style_emerald" type="string" /> - <public name="style_light" type="string" /> - <public name="style_dark" type="string" /> - <public name="style_satellite" type="string" /> - <public name="style_satellite_streets" type="string" /> + <public name="mapbox_style_mapbox_streets" type="string" /> + <public name="mapbox_style_emerald" type="string" /> + <public name="mapbox_style_light" type="string" /> + <public name="mapbox_style_dark" type="string" /> + <public name="mapbox_style_satellite" type="string" /> + <public name="mapbox_style_satellite_streets" type="string" /> <public name="center_longitude" type="attr" /> <public name="center_latitude" type="attr" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml new file mode 100644 index 0000000000..04c47af90d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + > + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/mapbox_attributionTelemetryMessage" + android:gravity="center_horizontal"/> + + <ListView + android:id="@+id/telemetryOptionsList" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + /> + +</LinearLayout> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml index 3e36cbf91a..a4fcc80681 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/infowindow_content.xml @@ -18,7 +18,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="2dp" android:maxEms="17" - android:text="@string/infoWindowTitle" + android:text="@string/mapbox_infoWindowTitle" android:textColor="@color/black" android:textSize="18sp" android:textStyle="bold" /> @@ -31,7 +31,7 @@ android:layout_marginTop="2dp" android:lineSpacingExtra="1dp" android:maxEms="17" - android:text="@string/infoWindowDescription" + android:text="@string/mapbox_infoWindowDescription" android:textColor="@color/gray" android:textSize="14sp" /> @@ -40,7 +40,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxEms="17" - android:text="@string/infoWindowAddress" + android:text="@string/mapbox_infoWindowAddress" android:textColor="@color/black" android:textSize="12sp" android:visibility="gone" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml index d62fd9cfba..288fb441ad 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapview_internal.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - <com.mapbox.mapboxsdk.maps.CompassView + <com.mapbox.mapboxsdk.maps.widgets.CompassView android:id="@+id/compassView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> @@ -15,7 +15,7 @@ android:id="@+id/logoView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="@string/mapboxIconContentDescription" + android:contentDescription="@string/mapbox_mapboxIconContentDescription" android:src="@drawable/attribution_logo" /> <ImageView @@ -24,12 +24,12 @@ android:layout_height="wrap_content" android:adjustViewBounds="true" android:clickable="true" - android:contentDescription="@string/attributionsIconContentDescription" + android:contentDescription="@string/mapbox_attributionsIconContentDescription" android:padding="7dp" android:src="@drawable/ic_info_outline_24dp_selector" android:background="@drawable/bg_default_selector"/> - <com.mapbox.mapboxsdk.maps.UserLocationView + <com.mapbox.mapboxsdk.maps.widgets.UserLocationView android:id="@+id/userLocationView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml index 2c1fdf8d13..d5d26d09d2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml @@ -4,11 +4,23 @@ <item>© Mapbox</item> <item>© OpenStreetMap</item> <item>Improve this map</item> + <item>Telemetry Settings</item> + </array> + <array name="attribution_telemetry_options"> + <item>Tell Me More</item> + <item>Don\'t Participate</item> + <item>Participate</item> + </array> + <array name="attribution_telemetry_options_already_participating"> + <item>Tell Me More</item> + <item>Stop Participating</item> + <item>Keep Participating</item> </array> <!-- If editing this array update MapView.ATTRIBUTION_INDEX_IMPROVE_THIS_MAP --> <array name="attribution_links" formatted="false" translatable="false"> <item>https://www.mapbox.com/about/maps/</item> <item>http://www.openstreetmap.org/about/</item> <item>https://www.mapbox.com/map-feedback/#/%1$f/%2$f/%3$d</item> + <item>https://www.mapbox.com/telemetry/</item> </array> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml index 63547247f3..f516e98dd8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml @@ -4,6 +4,8 @@ <attr name="center_longitude" format="float" /> <attr name="center_latitude" format="float" /> <attr name="zoom" format="float" /> + <attr name="zoom_max" format="float" /> + <attr name="zoom_min" format="float" /> <attr name="direction" format="float" /> <attr name="tilt" format="float" /> <attr name="zoom_enabled" format="boolean" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml index fd612d511c..5167ecf936 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/dimens.xml @@ -5,7 +5,8 @@ <dimen name="infowindow_offset">-2dp</dimen> <dimen name="infowindow_line_width">1.5dp</dimen> <dimen name="seven_dp">7dp</dimen> + <dimen name="eight_dp">8dp</dimen> <dimen name="ten_dp">10dp</dimen> <dimen name="sixteen_dp">16dp</dimen> <dimen name="seventy_six_dp">76dp</dimen> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml index eadcdcc043..f852c305b2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -1,18 +1,20 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="compassContentDescription">Map compass. Click to reset the map rotation to North.</string> - <string name="attributionsIconContentDescription">Attribution icon. Click to show attribution dialog.</string> - <string name="attributionsDialogTitle">Mapbox Android SDK</string> - <string name="mapboxIconContentDescription">The Mapbox logo.</string> - <string name="infoWindowTitle">Title</string> - <string name="infoWindowDescription">Description</string> - <string name="infoWindowAddress">Address</string> + <string name="mapbox_compassContentDescription">Map compass. Click to reset the map rotation to North.</string> + <string name="mapbox_attributionsIconContentDescription">Attribution icon. Click to show attribution dialog.</string> + <string name="mapbox_attributionsDialogTitle">Mapbox Android SDK</string> + <string name="mapbox_attributionTelemetryTitle">Make Mapbox Maps Better</string> + <string name="mapbox_attributionTelemetryMessage">You are helping to make OpenStreetMap and Mapbox maps better by contributing anonymous usage data.</string> + <string name="mapbox_mapboxIconContentDescription">The Mapbox logo.</string> + <string name="mapbox_infoWindowTitle">Title</string> + <string name="mapbox_infoWindowDescription">Description</string> + <string name="mapbox_infoWindowAddress">Address</string> <!-- these are public --> - <string name="style_mapbox_streets">mapbox://styles/mapbox/streets-v8</string> - <string name="style_emerald">mapbox://styles/mapbox/emerald-v8</string> - <string name="style_light">mapbox://styles/mapbox/light-v8</string> - <string name="style_dark">mapbox://styles/mapbox/dark-v8</string> - <string name="style_satellite">mapbox://styles/mapbox/satellite-v8</string> - <string name="style_satellite_streets">mapbox://styles/mapbox/satellite-hybrid-v8</string> + <string name="mapbox_style_mapbox_streets">mapbox://styles/mapbox/streets-v8</string> + <string name="mapbox_style_emerald">mapbox://styles/mapbox/emerald-v8</string> + <string name="mapbox_style_light">mapbox://styles/mapbox/light-v8</string> + <string name="mapbox_style_dark">mapbox://styles/mapbox/dark-v8</string> + <string name="mapbox_style_satellite">mapbox://styles/mapbox/satellite-v8</string> + <string name="mapbox_style_satellite_streets">mapbox://styles/mapbox/satellite-hybrid-v8</string> </resources> |