diff options
Diffstat (limited to 'platform/android')
60 files changed, 1650 insertions, 458 deletions
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index ff03df2236..b3b41ecbd1 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -4,11 +4,110 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to ## 5.2.0 - TBA -* Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) -* Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) -* Added `MapboxMap.getCameraForGeometry()` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) +## 5.2.0-beta.4 - November 3, 2017 + +- Revert adding mapbox-android-core dependency (#10354) [#10380](https://github.com/mapbox/mapbox-gl-native/pull/10380) +- Asynchronous TextureView [#10370](https://github.com/mapbox/mapbox-gl-native/pull/10370) +- Workaround OkHttp bug on Android O [10366](https://github.com/mapbox/mapbox-gl-native/pull/10366) +- Revisit logo resize [10553](https://github.com/mapbox/mapbox-gl-native/pull/10353) +- Logo resize for MapSnapshotter [#10312](https://github.com/mapbox/mapbox-gl-native/pull/10312) +- Make location provider optional [#10354](https://github.com/mapbox/mapbox-gl-native/pull/10354) +- Check for positive animation value [#10348](https://github.com/mapbox/mapbox-gl-native/pull/10348) +- Fix IAE of ease/animate [#10338](https://github.com/mapbox/mapbox-gl-native/pull/10338) +- Run full test suite on CI [#10333](https://github.com/mapbox/mapbox-gl-native/pull/10333) +- Make sure camera position gets updated in onFinish() callback after camera.move [#10324](https://github.com/mapbox/mapbox-gl-native/pull/10324) +- throw IAE in animate() and easeCamera() when duration <= 0 [#10321](https://github.com/mapbox/mapbox-gl-native/pull/10321) +- Don't save state if map hasn't been initialised [#10320](https://github.com/mapbox/mapbox-gl-native/pull/10320) +- Make map snapshot optional [#10310](https://github.com/mapbox/mapbox-gl-native/pull/10310) +- Synchronise locationlastions with Transifex [#10309](https://github.com/mapbox/mapbox-gl-native/pull/10309) +- MapboxMap#addImages [#10281](https://github.com/mapbox/mapbox-gl-native/pull/10281) +- Move shape annotation click handling to core [#10267](https://github.com/mapbox/mapbox-gl-native/pull/10267) +- Map snapshotter additions [#10163](https://github.com/mapbox/mapbox-gl-native/pull/10163) +- Add velocity to gestures / port animations to SDK animators [#10202](https://github.com/mapbox/mapbox-gl-native/pull/10202) +- Don't save state if map hasn't been initialised [#10320](https://github.com/mapbox/mapbox-gl-native/pull/10320) +- android.hardware.location.gps feature should not be required [#10347](https://github.com/mapbox/mapbox-gl-native/pull/10347) + +## 5.2.0-beta.3 - October 26, 2017 + +- Reorganize dependencies [#10268](https://github.com/mapbox/mapbox-gl-native/pull/10268) +- Blacklist VAO usage on adreno 3xx [#10291](https://github.com/mapbox/mapbox-gl-native/pull/10291) +- On stop null check [#10259](https://github.com/mapbox/mapbox-gl-native/pull/10259) + +## 5.2.0-beta.2 - October 19, 2017 + +- Wire up MapZoomButtonController with camera change events [#10221](https://github.com/mapbox/mapbox-gl-native/pull/10221) +- Execute callbacks only when not idle [#10220](https://github.com/mapbox/mapbox-gl-native/pull/10220) +- Cleanup unused gradle plugins [#10211](https://github.com/mapbox/mapbox-gl-native/pull/10211) +- add FileSource pause/resume [#9977](https://github.com/mapbox/mapbox-gl-native/pull/9977) +- add make target for ndk-stack [#10185](https://github.com/mapbox/mapbox-gl-native/pull/10185) +- Add interpolator examples [#10067](https://github.com/mapbox/mapbox-gl-native/pull/10067) +- Add an UnsatisfiedLinkError safeguard [#10180](https://github.com/mapbox/mapbox-gl-native/pull/10180) +- Hold off handling hover events untill map has been created [#10142](https://github.com/mapbox/mapbox-gl-native/pull/10142) +- Added `MapboxMap.getCameraForGeometry()` to get a camera with zoom level and center coordinate computed to fit a shape [#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107) +- Fine tune gesture zoom & rotation [#10134](https://github.com/mapbox/mapbox-gl-native/pull/10134) + +## 5.2.0-beta.1 - October 6, 2017 + +- Allow multiple listeners for camera events, deprecate old API [#10141](https://github.com/mapbox/mapbox-gl-native/pull/10141) +- Update symbol layer example with location [#10092](https://github.com/mapbox/mapbox-gl-native/pull/10092) +- Make OfflineTilePyramidRegionDefinition parceable [#10080](https://github.com/mapbox/mapbox-gl-native/pull/10080) +- Fix 5.2.0-SNAPSHOT CI build failing [#10079](https://github.com/mapbox/mapbox-gl-native/pull/10079) +- Deprecate MarkerView [#9782](https://github.com/mapbox/mapbox-gl-native/pull/9782) +- Hide overlain views on initalisation [#10068](https://github.com/mapbox/mapbox-gl-native/pull/10068) +- API for platform side animations [#10001](https://github.com/mapbox/mapbox-gl-native/pull/10001) +- Android asynchronous rendering [#9576](https://github.com/mapbox/mapbox-gl-native/pull/9576) +- Set error handler when starting snapshotter [#10035](https://github.com/mapbox/mapbox-gl-native/pull/10035) +- Hook camera events into compass [#10019](https://github.com/mapbox/mapbox-gl-native/pull/10019) +- Testapp cleanup [#10006](https://github.com/mapbox/mapbox-gl-native/pull/10006) +- Update zoom function example with selected state [#9987](https://github.com/mapbox/mapbox-gl-native/pull/9987) +- Add style inspection to debug activity [#9773](https://github.com/mapbox/mapbox-gl-native/pull/9773) +- Bump external dependencies [#9972](https://github.com/mapbox/mapbox-gl-native/pull/9972) +- Don't recycle bitmap for icon reuse. [#9966](https://github.com/mapbox/mapbox-gl-native/pull/9966) +- Android snapshotter [#9748](https://github.com/mapbox/mapbox-gl-native/pull/9748) +- Revert #9764 [#9851](https://github.com/mapbox/mapbox-gl-native/pull/9851) +- Update docs replacing Bitrise mentions with CircleCI [#9515](https://github.com/mapbox/mapbox-gl-native/pull/9515) +- Style image accessor [#9763](https://github.com/mapbox/mapbox-gl-native/pull/9763) +- Update readme with checkstyle and ndk-stack [#9788](https://github.com/mapbox/mapbox-gl-native/pull/9788) +- make android-check [#9787](https://github.com/mapbox/mapbox-gl-native/pull/9787) +- Deprecate MyLocationView in favor of LocationLayer plugin [#9771](https://github.com/mapbox/mapbox-gl-native/pull/9771) +- Increase firebase timeout for CI testing [#9774](https://github.com/mapbox/mapbox-gl-native/pull/9774) +- Restore max zoom to 25.5 [#9765](https://github.com/mapbox/mapbox-gl-native/pull/9765) +- Update example of camera zoom function on a symbol layer. [#9743](https://github.com/mapbox/mapbox-gl-native/pull/9743) +- Optimise icon management [#9643](https://github.com/mapbox/mapbox-gl-native/pull/9643) +- Expose setStyleJson and getStyleJson [#9714](https://github.com/mapbox/mapbox-gl-native/pull/9714) +- update LatLngBounds activity with BottomSheet interaction [#9736](https://github.com/mapbox/mapbox-gl-native/pull/9736) +- post updating InfoWindow update for InfoWindowAdapter [#9716](https://github.com/mapbox/mapbox-gl-native/pull/9716) +- Annotate MapboxMap class with UiThread [#9712](https://github.com/mapbox/mapbox-gl-native/pull/9712) +- Move ZoomButtonController creation to view initalisation [#9587](https://github.com/mapbox/mapbox-gl-native/pull/9587) +- Solve lint issues, reduce baseline [#9627](https://github.com/mapbox/mapbox-gl-native/pull/9627) +- Remove wear module from project [#9618](https://github.com/mapbox/mapbox-gl-native/pull/9618) +- Add zMediaOverlay configuration + bottom sheet integration [#9592](https://github.com/mapbox/mapbox-gl-native/pull/9592) +- Forward getMapAsync to map for fragment [#9621](https://github.com/mapbox/mapbox-gl-native/pull/9621) +- Make target for dumping system gfx information [#9616](https://github.com/mapbox/mapbox-gl-native/pull/9616) +- Make target documentation [#9617](https://github.com/mapbox/mapbox-gl-native/pull/9617) +- onGlobalLayout hook for map creation [#9607](https://github.com/mapbox/mapbox-gl-native/pull/9607) +- Custom viewpager for horizontal swiping [#9601](https://github.com/mapbox/mapbox-gl-native/pull/9601) +- Disable program caching on Adreno 3xx, 4xx, and 5xx GPUs due to known bugs [#9574](https://github.com/mapbox/mapbox-gl-native/pull/9574) +- Avoid creating InfoWindow iterator if no InfoWindows are shown [#9477](https://github.com/mapbox/mapbox-gl-native/pull/9477) +- Rewire map initialisation [#9462](https://github.com/mapbox/mapbox-gl-native/pull/9462) +- Trying to update non-existent polyline fix [#9544](https://github.com/mapbox/mapbox-gl-native/pull/9544) +- Location accuracy threshold [#9472](https://github.com/mapbox/mapbox-gl-native/pull/9472) +- Rewire gesture handling and telemetry event push [#9494](https://github.com/mapbox/mapbox-gl-native/pull/9494) +- run style instrumentation tests on CI [#9353](https://github.com/mapbox/mapbox-gl-native/pull/9353) +- Fix javadoc comment for public setOfflineMapboxTileCountLimit method [#9454](https://github.com/mapbox/mapbox-gl-native/pull/9454) +- add Map change & visibility test activities [#9425](https://github.com/mapbox/mapbox-gl-native/pull/9425) +- build release package once during ci build [#9351](https://github.com/mapbox/mapbox-gl-native/pull/9351) +- Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) +- Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835) + +## 5.1.5 - October 31, 2017 + +* Remove obsolete terminate context/display calls [#10162](https://github.com/mapbox/mapbox-gl-native/pull/10162) +* Determine need for clip ID based on actual layers/tiles [#10216](https://github.com/mapbox/mapbox-gl-native/pull/10216) +* Correctly alter sprite URLs [#10217](https://github.com/mapbox/mapbox-gl-native/pull/10217) +* Russian and Ukrainian localizations [#9945](https://github.com/mapbox/mapbox-gl-native/pull/9945) -### 5.1.4 - September 25, 2017 +## 5.1.4 - September 25, 2017 * Update translations [#10033](https://github.com/mapbox/mapbox-gl-native/pull/10033) & [#9945](https://github.com/mapbox/mapbox-gl-native/pull/9945) * Continue rendering tiles despite erros [#10012](https://github.com/mapbox/mapbox-gl-native/pull/10012) @@ -101,7 +200,7 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to * Infinite location animation updates [#9194](https://github.com/mapbox/mapbox-gl-native/pull/9194) * Invoke callback with valid fling gestures [#9192](https://github.com/mapbox/mapbox-gl-native/pull/9192) * Keep location tracking after screen rotation [#9187](https://github.com/mapbox/mapbox-gl-native/pull/9187) -* Update components with camera values when animating [#9174](https://github.com/mapbox/mapbox-gl-native/pull/9174) +* Update components with camera values when animating [#9174](https://github.com/mapbox/mapbox-gl-native/pull/9174) * Validate if gestures should execute [#9173](https://github.com/mapbox/mapbox-gl-native/pull/9173) * Custom location source and LOST integration [#9142](https://github.com/mapbox/mapbox-gl-native/pull/9142) @@ -503,12 +602,6 @@ Mapbox Android 4.0.0 is the most ambitious Android release to date with 3 major - Satellite Streets Style ([#2739](https://github.com/mapbox/mapbox-gl-native/issues/2739)) - **RESOLVED** Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802)) - ## 2.1.0 - October 21, 2015 - Initial Android release. - -Known issues: - -- Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802)) - - Resolved in 2.2.0 diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index 688c632326..f3e9433a0b 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -2,8 +2,7 @@ apply plugin: 'com.android.library' dependencies { compile rootProject.ext.dep.supportAnnotations - compile rootProject.ext.dep.supportV4 - compile rootProject.ext.dep.supportDesign + compile rootProject.ext.dep.supportFragmentV4 compile rootProject.ext.dep.timber compile rootProject.ext.dep.okhttp3 provided(rootProject.ext.dep.lost) { @@ -21,6 +20,7 @@ dependencies { // Mapbox Android Services (Telemetry support) compile(rootProject.ext.dep.mapboxAndroidTelemetry) { transitive = true + exclude group: 'com.android.support' } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml index 231e36e092..b61035a008 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ <uses-feature android:glEsVersion="0x00020000" android:required="true" /> <uses-feature android:name="android.hardware.wifi" android:required="false" /> <!-- Implied by ACCESS_WIFI_STATE. --> + <uses-feature android:name="android.hardware.location.gps" android:required="false"/> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java index 8a75176ccd..a024f0ab70 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java @@ -1,5 +1,7 @@ package com.mapbox.mapboxsdk; +import timber.log.Timber; + /** * Centralises the knowledge about "mapbox-gl" library loading. */ @@ -9,7 +11,10 @@ public class LibraryLoader { * Loads "libmapbox-gl.so" native shared library. */ public static void load() { - System.loadLibrary("mapbox-gl"); + try { + System.loadLibrary("mapbox-gl"); + } catch (UnsatisfiedLinkError error) { + Timber.e(error, "Failed to load native shared library."); + } } - } 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 97a9ea94ee..fc448ccf7b 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 @@ -80,12 +80,12 @@ public class MapboxConstants { /** * The currently used minimun scale factor to clamp to when a quick zoom gesture occurs */ - public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.65f; + public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.00f; /** * The currently used maximum scale factor to clamp to when a quick zoom gesture occurs */ - public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 1.35f; + public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.45f; /** * Fragment Argument Key for MapboxMapOptions diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java index e2626a026b..32aa250997 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java @@ -85,7 +85,14 @@ class HTTPRequest implements Callback { } mRequest = builder.build(); mCall = mClient.newCall(mRequest); - mCall.enqueue(this); + + // TODO remove code block for workaround in #10303 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { + mCall.enqueue(this); + } else { + // Calling execute instead of enqueue is a workaround for #10303 + onResponse(mCall, mCall.execute()); + } } catch (Exception exception) { onFailure(exception); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index c09c926eb5..9f256c341b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -21,7 +21,6 @@ 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.services.commons.geojson.Feature; import java.util.ArrayList; import java.util.List; @@ -41,7 +40,6 @@ import timber.log.Timber; */ class AnnotationManager { - private static final String LAYER_ID_SHAPE_ANNOTATIONS = "com.mapbox.annotations.shape."; private static final long NO_ANNOTATION_ID = -1; private final MapView mapView; @@ -50,7 +48,6 @@ class AnnotationManager { private final MarkerViewManager markerViewManager; private final LongSparseArray<Annotation> annotationsArray; private final List<Marker> selectedMarkers = new ArrayList<>(); - private final List<String> shapeAnnotationIds = new ArrayList<>(); private MapboxMap mapboxMap; private MapboxMap.OnMarkerClickListener onMarkerClickListener; @@ -58,13 +55,14 @@ class AnnotationManager { private MapboxMap.OnPolylineClickListener onPolylineClickListener; private Annotations annotations; + private ShapeAnnotations shapeAnnotations; private Markers markers; private Polygons polygons; private Polylines polylines; AnnotationManager(NativeMapView view, MapView mapView, LongSparseArray<Annotation> annotationsArray, MarkerViewManager markerViewManager, IconManager iconManager, Annotations annotations, - Markers markers, Polygons polygons, Polylines polylines) { + Markers markers, Polygons polygons, Polylines polylines, ShapeAnnotations shapeAnnotations) { this.mapView = mapView; this.annotationsArray = annotationsArray; this.markerViewManager = markerViewManager; @@ -73,6 +71,7 @@ class AnnotationManager { this.markers = markers; this.polygons = polygons; this.polylines = polylines; + this.shapeAnnotations = shapeAnnotations; if (view != null) { // null checking needed for unit tests view.addOnMapChangedListener(markerViewManager); @@ -122,9 +121,6 @@ class AnnotationManager { // do icon cleanup iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } annotations.removeBy(annotation); } @@ -143,9 +139,6 @@ class AnnotationManager { } else { iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } } annotations.removeBy(annotationList); @@ -167,9 +160,6 @@ class AnnotationManager { } else { iconManager.iconCleanup(marker.getIcon()); } - } else { - // instanceOf Polygon/Polyline - shapeAnnotationIds.remove(annotation.getId()); } } annotations.removeAll(); @@ -227,17 +217,11 @@ class AnnotationManager { // Polygon addPolygon(@NonNull PolygonOptions polygonOptions, @NonNull MapboxMap mapboxMap) { - Polygon polygon = polygons.addBy(polygonOptions, mapboxMap); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); - return polygon; + return polygons.addBy(polygonOptions, mapboxMap); } List<Polygon> addPolygons(@NonNull List<PolygonOptions> polygonOptionsList, @NonNull MapboxMap mapboxMap) { - List<Polygon> polygonList = polygons.addBy(polygonOptionsList, mapboxMap); - for (Polygon polygon : polygonList) { - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polygon.getId()); - } - return polygonList; + return polygons.addBy(polygonOptionsList, mapboxMap); } void updatePolygon(Polygon polygon) { @@ -257,17 +241,11 @@ class AnnotationManager { // Polyline addPolyline(@NonNull PolylineOptions polylineOptions, @NonNull MapboxMap mapboxMap) { - Polyline polyline = polylines.addBy(polylineOptions, mapboxMap); - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); - return polyline; + return polylines.addBy(polylineOptions, mapboxMap); } List<Polyline> addPolylines(@NonNull List<PolylineOptions> polylineOptionsList, @NonNull MapboxMap mapboxMap) { - List<Polyline> polylineList = polylines.addBy(polylineOptionsList, mapboxMap); - for (Polyline polyline : polylineList) { - shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + polyline.getId()); - } - return polylineList; + return polylines.addBy(polylineOptionsList, mapboxMap); } void updatePolyline(Polyline polyline) { @@ -397,11 +375,11 @@ class AnnotationManager { // boolean onTap(PointF tapPoint) { - if (!shapeAnnotationIds.isEmpty()) { - ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); - long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); - if (shapeAnnotationId != NO_ANNOTATION_ID) { - handleClickForShapeAnnotation(shapeAnnotationId); + ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); + Annotation annotation = new ShapeAnnotationHitResolver(shapeAnnotations).execute(shapeAnnotationHit); + if (annotation != null) { + if (handleClickForShapeAnnotation(annotation)) { + return true; } } @@ -418,16 +396,18 @@ class AnnotationManager { tapPoint.x + touchTargetSide, tapPoint.y + touchTargetSide ); - return new ShapeAnnotationHit(tapRect, shapeAnnotationIds.toArray(new String[shapeAnnotationIds.size()])); + return new ShapeAnnotationHit(tapRect); } - private void handleClickForShapeAnnotation(long shapeAnnotationId) { - Annotation annotation = getAnnotation(shapeAnnotationId); + private boolean handleClickForShapeAnnotation(Annotation annotation) { if (annotation instanceof Polygon && onPolygonClickListener != null) { onPolygonClickListener.onPolygonClick((Polygon) annotation); + return true; } else if (annotation instanceof Polyline && onPolylineClickListener != null) { onPolylineClickListener.onPolylineClick((Polyline) annotation); + return true; } + return false; } private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) { @@ -470,28 +450,19 @@ class AnnotationManager { private static class ShapeAnnotationHitResolver { - private MapboxMap mapboxMap; - - ShapeAnnotationHitResolver(MapboxMap mapboxMap) { - this.mapboxMap = mapboxMap; - } + private ShapeAnnotations shapeAnnotations; - public long execute(ShapeAnnotationHit shapeHit) { - long foundAnnotationId = NO_ANNOTATION_ID; - List<Feature> features = mapboxMap.queryRenderedFeatures(shapeHit.tapPoint, shapeHit.layerIds); - if (!features.isEmpty()) { - foundAnnotationId = getIdFromFeature(features.get(0)); - } - return foundAnnotationId; + ShapeAnnotationHitResolver(ShapeAnnotations shapeAnnotations) { + this.shapeAnnotations = shapeAnnotations; } - private long getIdFromFeature(Feature feature) { - try { - return Long.valueOf(feature.getId()); - } catch (NumberFormatException exception) { - Timber.e(exception, "Couldn't parse feature id to a long, with id: %s", feature.getId()); - return NO_ANNOTATION_ID; + public Annotation execute(ShapeAnnotationHit shapeHit) { + Annotation foundAnnotation = null; + List<Annotation> annotations = shapeAnnotations.obtainAllIn(shapeHit.tapPoint); + if (annotations.size() > 0) { + foundAnnotation = annotations.get(0); } + return foundAnnotation; } } @@ -567,11 +538,9 @@ class AnnotationManager { private static class ShapeAnnotationHit { private final RectF tapPoint; - private final String[] layerIds; - ShapeAnnotationHit(RectF tapRect, String[] layerIds) { - this.tapPoint = tapRect; - this.layerIds = layerIds; + ShapeAnnotationHit(RectF tapPoint) { + this.tapPoint = tapPoint; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java index 5113a0cc73..9ccff387f5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java @@ -1,12 +1,12 @@ package com.mapbox.mapboxsdk.maps; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -56,7 +56,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. private void showAttributionDialog() { attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionsDialogTitle); builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this); builder.show(); @@ -77,7 +77,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. } private void showTelemetryDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionTelemetryTitle); builder.setMessage(R.string.mapbox_attributionTelemetryMessage); builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java index 6f7d7c0080..cf780dcc3f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java @@ -1,5 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveCanceledListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; @@ -10,23 +15,32 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M private boolean idle = true; + private final List<OnCameraMoveStartedListener> onCameraMoveStartedListenerList = new ArrayList<>(); + private final List<OnCameraMoveCanceledListener> onCameraMoveCanceledListenerList = new ArrayList<>(); + private final List<OnCameraMoveListener> onCameraMoveListenerList = new ArrayList<>(); + private final List<OnCameraIdleListener> onCameraIdleListenerList = new ArrayList<>(); + private OnCameraMoveStartedListener onCameraMoveStartedListener; private OnCameraMoveCanceledListener onCameraMoveCanceledListener; private OnCameraMoveListener onCameraMoveListener; private OnCameraIdleListener onCameraIdleListener; + @Deprecated void setOnCameraMoveStartedListener(OnCameraMoveStartedListener onCameraMoveStartedListener) { this.onCameraMoveStartedListener = onCameraMoveStartedListener; } + @Deprecated void setOnCameraMoveCanceledListener(OnCameraMoveCanceledListener onCameraMoveCanceledListener) { this.onCameraMoveCanceledListener = onCameraMoveCanceledListener; } + @Deprecated void setOnCameraMoveListener(OnCameraMoveListener onCameraMoveListener) { this.onCameraMoveListener = onCameraMoveListener; } + @Deprecated void setOnCameraIdleListener(OnCameraIdleListener onCameraIdleListener) { this.onCameraIdleListener = onCameraIdleListener; } @@ -36,34 +50,106 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (!idle) { return; } - idle = false; + + // deprecated API if (onCameraMoveStartedListener != null) { onCameraMoveStartedListener.onCameraMoveStarted(reason); } + + // new API + if (!onCameraMoveStartedListenerList.isEmpty()) { + for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStartedListenerList) { + cameraMoveStartedListener.onCameraMoveStarted(reason); + } + } } @Override public void onCameraMove() { + // deprecated API if (onCameraMoveListener != null && !idle) { onCameraMoveListener.onCameraMove(); } + + // new API + if (!onCameraMoveListenerList.isEmpty() && !idle) { + for (OnCameraMoveListener cameraMoveListener : onCameraMoveListenerList) { + cameraMoveListener.onCameraMove(); + } + } } @Override public void onCameraMoveCanceled() { + // deprecated API if (onCameraMoveCanceledListener != null && !idle) { onCameraMoveCanceledListener.onCameraMoveCanceled(); } + + // new API + if (!onCameraMoveCanceledListenerList.isEmpty() && !idle) { + for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceledListenerList) { + cameraMoveCanceledListener.onCameraMoveCanceled(); + } + } } @Override public void onCameraIdle() { if (!idle) { idle = true; + // deprecated API if (onCameraIdleListener != null) { onCameraIdleListener.onCameraIdle(); } + + // new API + if (!onCameraIdleListenerList.isEmpty()) { + for (OnCameraIdleListener cameraIdleListener : onCameraIdleListenerList) { + cameraIdleListener.onCameraIdle(); + } + } + } + } + + void addOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + onCameraIdleListenerList.add(listener); + } + + void removeOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + if (onCameraIdleListenerList.contains(listener)) { + onCameraIdleListenerList.remove(listener); + } + } + + void addOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + onCameraMoveCanceledListenerList.add(listener); + } + + void removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + if (onCameraMoveCanceledListenerList.contains(listener)) { + onCameraMoveCanceledListenerList.remove(listener); + } + } + + void addOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + onCameraMoveStartedListenerList.add(listener); + } + + void removeOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + if (onCameraMoveStartedListenerList.contains(listener)) { + onCameraMoveStartedListenerList.remove(listener); + } + } + + void addOnCameraMoveListener(OnCameraMoveListener listener) { + onCameraMoveListenerList.add(listener); + } + + void removeOnCameraMoveListener(OnCameraMoveListener listener) { + if (onCameraMoveListenerList.contains(listener)) { + onCameraMoveListenerList.remove(listener); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index b1d6df2103..80ffa973e7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -143,11 +143,14 @@ class IconManager { } void iconCleanup(Icon icon) { - int refCounter = iconMap.get(icon) - 1; - if (refCounter == 0) { - remove(icon); - } else { - updateIconRefCounter(icon, refCounter); + Integer refCounter = iconMap.get(icon); + if (refCounter != null) { + refCounter--; + if (refCounter == 0) { + remove(icon); + } else { + updateIconRefCounter(icon, refCounter); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java new file mode 100644 index 0000000000..b2f6cef3b0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java @@ -0,0 +1,17 @@ +package com.mapbox.mapboxsdk.maps; + +class Image { + private final byte[] buffer; + private final float pixelRatio; + private final String name; + private final int width; + private final int height; + + public Image(byte[] buffer, float pixelRatio, String name, int width, int height) { + this.buffer = buffer; + this.pixelRatio = pixelRatio; + this.name = name; + this.width = width; + this.height = height; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index 2394e52193..4120e164a4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -1,14 +1,19 @@ package com.mapbox.mapboxsdk.maps; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.PointF; import android.location.Location; import android.support.annotation.Nullable; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; +import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ScaleGestureDetector; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; @@ -57,8 +62,14 @@ final class MapGestureDetector { private boolean scaleGestureOccurred; private boolean recentScaleGestureOccurred; + private boolean scaleAnimating; private long scaleBeginTime; + private VelocityTracker velocityTracker; + private boolean wasZoomingIn; + private boolean wasClockwiseRotating; + private boolean rotateGestureOccurred; + MapGestureDetector(Context context, Transform transform, Projection projection, UiSettings uiSettings, TrackingSettings trackingSettings, AnnotationManager annotationManager, CameraChangeDispatcher cameraChangeDispatcher) { @@ -153,6 +164,12 @@ final class MapGestureDetector { // Handle two finger tap switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } else { + velocityTracker.clear(); + } + velocityTracker.addMovement(event); // First pointer down, reset scaleGestureOccurred, used to avoid triggering a fling after a scale gesture #7666 recentScaleGestureOccurred = false; transform.setGestureInProgress(true); @@ -203,11 +220,23 @@ final class MapGestureDetector { twoTap = false; transform.setGestureInProgress(false); + if (velocityTracker != null) { + velocityTracker.recycle(); + } + velocityTracker = null; break; case MotionEvent.ACTION_CANCEL: twoTap = false; transform.setGestureInProgress(false); + if (velocityTracker != null) { + velocityTracker.recycle(); + } + velocityTracker = null; + break; + case MotionEvent.ACTION_MOVE: + velocityTracker.addMovement(event); + velocityTracker.computeCurrentVelocity(1000); break; } @@ -430,7 +459,11 @@ final class MapGestureDetector { */ private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + private static final int ANIMATION_TIME_MULTIPLIER = 77; + private static final double ZOOM_DISTANCE_DIVIDER = 5; + private float scaleFactor = 1.0f; + private PointF scalePointBegin; // Called when two fingers first touch the screen @Override @@ -440,6 +473,7 @@ final class MapGestureDetector { } recentScaleGestureOccurred = true; + scalePointBegin = new PointF(detector.getFocusX(), detector.getFocusY()); scaleBeginTime = detector.getEventTime(); MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), @@ -447,15 +481,6 @@ final class MapGestureDetector { return true; } - // Called when fingers leave screen - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - scaleGestureOccurred = false; - scaleBeginTime = 0; - scaleFactor = 1.0f; - cameraChangeDispatcher.onCameraIdle(); - } - // Called each time a finger moves // Called for pinch zooms and quickzooms/quickscales @Override @@ -464,6 +489,7 @@ final class MapGestureDetector { return super.onScale(detector); } + wasZoomingIn = (Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2)) >= 0; if (tiltGestureOccurred) { return false; } @@ -478,7 +504,7 @@ final class MapGestureDetector { // If scale is large enough ignore a tap scaleFactor *= detector.getScaleFactor(); - if ((scaleFactor > 1.05f) || (scaleFactor < 0.95f)) { + if ((scaleFactor > 1.1f) || (scaleFactor < 0.9f)) { // notify camera change listener cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); scaleGestureOccurred = true; @@ -497,7 +523,6 @@ final class MapGestureDetector { // make an assumption here; if the zoom center is specified by the gesture, it's NOT going // to be in the center of the map. Therefore the zoom will translate the map center, so tracking // should be disabled. - trackingSettings.resetTrackingModesIfRequired(!quickZoom, false, false); // Scale the map if (focalPoint != null) { @@ -506,19 +531,79 @@ final class MapGestureDetector { } else if (quickZoom) { cameraChangeDispatcher.onCameraMove(); // clamp scale factors we feed to core #7514 - float scaleFactor = MathUtils.clamp(detector.getScaleFactor(), + float scaleFactor = detector.getScaleFactor(); + // around center map + double zoomBy = Math.log(scaleFactor) / Math.log(Math.PI / 2); + boolean negative = zoomBy < 0; + zoomBy = MathUtils.clamp(Math.abs(zoomBy), MapboxConstants.MINIMUM_SCALE_FACTOR_CLAMP, MapboxConstants.MAXIMUM_SCALE_FACTOR_CLAMP); - // around center map - transform.zoomBy(Math.log(scaleFactor) / Math.log(Math.PI / 2), - uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + transform.zoomBy(negative ? -zoomBy : zoomBy, uiSettings.getWidth() / 2, uiSettings.getHeight() / 2); + recentScaleGestureOccurred = true; } else { // around gesture transform.zoomBy(Math.log(detector.getScaleFactor()) / Math.log(Math.PI / 2), - detector.getFocusX(), detector.getFocusY()); + scalePointBegin.x, scalePointBegin.y); } return true; } + + // Called when fingers leave screen + @Override + public void onScaleEnd(final ScaleGestureDetector detector) { + if (rotateGestureOccurred || quickZoom) { + reset(); + return; + } + + double velocityXY = Math.abs(velocityTracker.getYVelocity()) + Math.abs(velocityTracker.getXVelocity()); + if (velocityXY > MapboxConstants.VELOCITY_THRESHOLD_IGNORE_FLING / 2) { + scaleAnimating = true; + double zoomAddition = calculateScale(velocityXY); + double currentZoom = transform.getRawZoom(); + long animationTime = (long) (Math.log(velocityXY) * ANIMATION_TIME_MULTIPLIER); + createScaleAnimator(currentZoom, zoomAddition, animationTime).start(); + } else if (!scaleAnimating) { + reset(); + } + } + + private void reset() { + scaleAnimating = false; + scaleGestureOccurred = false; + scaleBeginTime = 0; + scaleFactor = 1.0f; + cameraChangeDispatcher.onCameraIdle(); + } + + private double calculateScale(double velocityXY) { + double zoomAddition = (float) (Math.log(velocityXY) / ZOOM_DISTANCE_DIVIDER); + if (!wasZoomingIn) { + zoomAddition = -zoomAddition; + } + return zoomAddition; + } + + private Animator createScaleAnimator(double currentZoom, double zoomAddition, long animationTime) { + ValueAnimator animator = ValueAnimator.ofFloat((float) currentZoom, (float) (currentZoom + zoomAddition)); + animator.setDuration(animationTime); + animator.setInterpolator(new FastOutSlowInInterpolator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transform.setZoom((Float) animation.getAnimatedValue(), scalePointBegin); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + return animator; + } } /** @@ -526,11 +611,13 @@ final class MapGestureDetector { */ private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { - private static final long ROTATE_INVOKE_WAIT_TIME = 1500; + private static final float ROTATE_INVOKE_ANGLE = 15.30f; + private static final float ROTATE_LIMITATION_ANGLE = 3.35f; + private static final float ROTATE_LIMITATION_DURATION = ROTATE_LIMITATION_ANGLE * 1.85f; private long beginTime = 0; - private float totalAngle = 0.0f; private boolean started = false; + private boolean animating = false; // Called when two fingers first touch the screen @Override @@ -546,15 +633,6 @@ final class MapGestureDetector { return true; } - // Called when the fingers leave the screen - @Override - public void onRotateEnd(RotateGestureDetector detector) { - // notify camera change listener - beginTime = 0; - totalAngle = 0.0f; - started = false; - } - // Called each time one of the two fingers moves // Called for rotation @Override @@ -563,18 +641,10 @@ final class MapGestureDetector { return false; } - // Ignore short touches in case it is a tap - // Also ignore small rotate - long time = detector.getEventTime(); - long interval = time - beginTime; - if (!started && (interval <= ViewConfiguration.getTapTimeout() || isScaleGestureActive(time))) { - return false; - } - // If rotate is large enough ignore a tap // Also is zoom already started, don't rotate - totalAngle += detector.getRotationDegreesDelta(); - if (totalAngle > 35.0f || totalAngle < -35.0f) { + float angle = detector.getRotationDegreesDelta(); + if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) { MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), MapboxEvent.GESTURE_ROTATION_START, transform)); @@ -585,13 +655,17 @@ final class MapGestureDetector { return false; } + wasClockwiseRotating = detector.getRotationDegreesDelta() > 0; + if (scaleBeginTime != 0) { + rotateGestureOccurred = true; + } + // rotation constitutes translation of anything except the center of // rotation, so cancel both location and bearing tracking if required trackingSettings.resetTrackingModesIfRequired(true, true, false); - // Get rotate value - double bearing = transform.getRawBearing(); - bearing += detector.getRotationDegreesDelta(); + // Calculate map bearing value + double bearing = transform.getRawBearing() + angle; // Rotate the map if (focalPoint != null) { @@ -604,11 +678,81 @@ final class MapGestureDetector { return true; } - private boolean isScaleGestureActive(long time) { - long scaleExecutionTime = time - scaleBeginTime; - boolean scaleGestureStarted = scaleBeginTime != 0; - boolean scaleOffsetTimeValid = scaleExecutionTime > ROTATE_INVOKE_WAIT_TIME; - return (scaleGestureStarted && scaleOffsetTimeValid) || scaleGestureOccurred; + // Called when the fingers leave the screen + @Override + public void onRotateEnd(RotateGestureDetector detector) { + long interval = detector.getEventTime() - beginTime; + if ((!started && (interval <= ViewConfiguration.getTapTimeout())) || scaleAnimating || interval > 500) { + reset(); + return; + } + + double angularVelocity = calculateVelocityVector(detector); + if (Math.abs(angularVelocity) > 0.001 && rotateGestureOccurred && !animating) { + animateRotateVelocity(); + } else if (!animating) { + reset(); + } + } + + private void reset() { + beginTime = 0; + started = false; + animating = false; + rotateGestureOccurred = false; + } + + private void animateRotateVelocity() { + animating = true; + double currentRotation = transform.getRawBearing(); + double rotateAdditionDegrees = calculateVelocityInDegrees(); + createAnimator(currentRotation, rotateAdditionDegrees).start(); + } + + private double calculateVelocityVector(RotateGestureDetector detector) { + return ((detector.getFocusX() * velocityTracker.getYVelocity()) + + (detector.getFocusY() * velocityTracker.getXVelocity())) + / (Math.pow(detector.getFocusX(), 2) + Math.pow(detector.getFocusY(), 2)); + } + + private double calculateVelocityInDegrees() { + double angleRadians = Math.atan2(velocityTracker.getXVelocity(), velocityTracker.getYVelocity()); + double angle = angleRadians / (Math.PI / 180); + if (angle <= 0) { + angle += 360; + } + + // limit the angle + angle = angle / ROTATE_LIMITATION_ANGLE; + + // correct direction + if (!wasClockwiseRotating) { + angle = -angle; + } + + return angle; + } + + private Animator createAnimator(double currentRotation, double rotateAdditionDegrees) { + ValueAnimator animator = ValueAnimator.ofFloat( + (float) currentRotation, + (float) (currentRotation + rotateAdditionDegrees) + ); + animator.setDuration((long) (Math.abs(rotateAdditionDegrees) * ROTATE_LIMITATION_DURATION)); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + transform.setBearing((Float) animation.getAnimatedValue()); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + reset(); + } + }); + return animator; } } 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 92153f7f0b..c025a119b7 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 @@ -28,13 +28,14 @@ import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; -import com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer; import com.mapbox.mapboxsdk.maps.renderer.MapRenderer; +import com.mapbox.mapboxsdk.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer; import com.mapbox.mapboxsdk.maps.renderer.textureview.TextureViewMapRenderer; import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; +import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.services.android.telemetry.MapboxTelemetry; import java.lang.annotation.Retention; @@ -175,8 +176,9 @@ public class MapView extends FrameLayout { Markers markers = new MarkerContainer(nativeMapView, this, annotationsArray, iconManager, markerViewManager); Polygons polygons = new PolygonContainer(nativeMapView, annotationsArray); Polylines polylines = new PolylineContainer(nativeMapView, annotationsArray); + ShapeAnnotations shapeAnnotations = new ShapeAnnotationContainer(nativeMapView, annotationsArray); AnnotationManager annotationManager = new AnnotationManager(nativeMapView, this, annotationsArray, - markerViewManager, iconManager, annotations, markers, polygons, polylines); + markerViewManager, iconManager, annotations, markers, polygons, polylines, shapeAnnotations); Transform transform = new Transform(nativeMapView, annotationManager.getMarkerViewManager(), trackingSettings, cameraChangeDispatcher); @@ -191,8 +193,10 @@ public class MapView extends FrameLayout { annotationManager, cameraChangeDispatcher); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); + // overlain zoom buttons mapZoomButtonController = new MapZoomButtonController(new ZoomButtonsController(this)); - MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform); + MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform, + cameraChangeDispatcher, getWidth(), getHeight()); mapZoomButtonController.bind(uiSettings, zoomListener); compassView.injectCompassAnimationListener(createCompassAnimationListener(cameraChangeDispatcher)); @@ -345,8 +349,10 @@ public class MapView extends FrameLayout { */ @UiThread public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); - mapboxMap.onSaveInstanceState(outState); + if (mapboxMap != null) { + outState.putBoolean(MapboxConstants.STATE_HAS_SAVED_STATE, true); + mapboxMap.onSaveInstanceState(outState); + } } /** @@ -355,6 +361,7 @@ public class MapView extends FrameLayout { @UiThread public void onStart() { ConnectivityReceiver.instance(getContext()).activate(); + FileSource.getInstance(getContext()).activate(); if (mapboxMap != null) { mapboxMap.onStart(); } @@ -385,8 +392,12 @@ public class MapView extends FrameLayout { */ @UiThread public void onStop() { - mapboxMap.onStop(); + if (mapboxMap != null) { + // map was destroyed before it was started + mapboxMap.onStop(); + } ConnectivityReceiver.instance(getContext()).deactivate(); + FileSource.getInstance(getContext()).deactivate(); } /** @@ -406,6 +417,10 @@ public class MapView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (!isMapInitialized() || !isZoomButtonControllerInitialized()) { + return super.onTouchEvent(event); + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { mapZoomButtonController.setVisible(true); } @@ -434,11 +449,18 @@ public class MapView extends FrameLayout { @Override public boolean onGenericMotionEvent(MotionEvent event) { + if (mapGestureDetector == null) { + return super.onGenericMotionEvent(event); + } return mapGestureDetector.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } @Override public boolean onHoverEvent(MotionEvent event) { + if (!isZoomButtonControllerInitialized()) { + return super.onHoverEvent(event); + } + switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: @@ -495,7 +517,7 @@ public class MapView extends FrameLayout { if (destroyed) { return; } - if (nativeMapView == null) { + if (!isMapInitialized()) { mapboxMapOptions.styleUrl(url); return; } @@ -512,7 +534,7 @@ public class MapView extends FrameLayout { return; } - if (!isInEditMode() && nativeMapView != null) { + if (!isInEditMode() && isMapInitialized()) { nativeMapView.resizeView(width, height); } } @@ -526,7 +548,9 @@ public class MapView extends FrameLayout { @CallSuper protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mapZoomButtonController.setVisible(false); + if (isZoomButtonControllerInitialized()) { + mapZoomButtonController.setVisible(false); + } } // Called when view is hidden and shown @@ -536,7 +560,7 @@ public class MapView extends FrameLayout { return; } - if (mapZoomButtonController != null) { + if (isZoomButtonControllerInitialized()) { mapZoomButtonController.setVisible(visibility == View.VISIBLE); } } @@ -598,6 +622,14 @@ public class MapView extends FrameLayout { } } + private boolean isMapInitialized() { + return nativeMapView != null; + } + + private boolean isZoomButtonControllerInitialized() { + return mapZoomButtonController != null; + } + MapboxMap getMapboxMap() { return mapboxMap; } @@ -883,16 +915,23 @@ public class MapView extends FrameLayout { } } - private class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { + private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { private final MapGestureDetector mapGestureDetector; private final UiSettings uiSettings; private final Transform transform; + private final CameraChangeDispatcher cameraChangeDispatcher; + private final float mapWidth; + private final float mapHeight; - MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform) { + MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform, + CameraChangeDispatcher dispatcher, float mapWidth, float mapHeight) { this.mapGestureDetector = detector; this.uiSettings = uiSettings; this.transform = transform; + this.cameraChangeDispatcher = dispatcher; + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; } // Not used @@ -905,6 +944,7 @@ public class MapView extends FrameLayout { @Override public void onZoom(boolean zoomIn) { if (uiSettings.isZoomGesturesEnabled()) { + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_ANIMATION); onZoom(zoomIn, mapGestureDetector.getFocalPoint()); } } @@ -913,7 +953,7 @@ public class MapView extends FrameLayout { if (focalPoint != null) { transform.zoom(zoomIn, focalPoint); } else { - PointF centerPoint = new PointF(getMeasuredWidth() / 2, getMeasuredHeight() / 2); + PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2); transform.zoom(zoomIn, centerPoint); } } 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 83b4dc4e84..6bf8342efb 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 @@ -47,6 +47,7 @@ import com.mapbox.services.commons.geojson.Feature; import com.mapbox.services.commons.geojson.Geometry; import java.lang.reflect.ParameterizedType; +import java.util.HashMap; import java.util.List; import timber.log.Timber; @@ -463,6 +464,13 @@ public final class MapboxMap { } /** + * Adds an images to be used in the map's style + */ + public void addImages(@NonNull HashMap<String, Bitmap> images) { + nativeMapView.addImages(images); + } + + /** * Removes an image from the map's style * * @param name the name of the image to remove @@ -709,6 +717,10 @@ public final class MapboxMap { // MapChange.REGION_DID_CHANGE_ANIMATED is not called for `jumpTo` // invalidate camera position to provide OnCameraChange event. invalidateCameraPosition(); + + if (callback != null) { + callback.onFinish(); + } } }); } @@ -1753,39 +1765,123 @@ public final class MapboxMap { * Sets a callback that is invoked when camera movement has ended. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraIdleListener(OnCameraIdleListener)} + * and {@link #removeOnCameraIdleListener(OnCameraIdleListener)} instead */ + @Deprecated public void setOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { cameraChangeDispatcher.setOnCameraIdleListener(listener); } /** + * Adds a callback that is invoked when camera movement has ended. + * + * @param listener the listener to notify + */ + public void addOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.addOnCameraIdleListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has ended. + * + * @param listener the listener to remove + */ + public void removeOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.removeOnCameraIdleListener(listener); + } + + /** * Sets a callback that is invoked when camera movement was cancelled. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} and + * {@link #removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} instead */ + @Deprecated public void setOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { cameraChangeDispatcher.setOnCameraMoveCanceledListener(listener); } /** + * Adds a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.addOnCameraMoveCancelListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.removeOnCameraMoveCancelListener(listener); + } + + /** * Sets a callback that is invoked when camera movement has started. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveStartedListener(OnCameraMoveStartedListener)} and + * {@link #removeOnCameraMoveStartedListener(OnCameraMoveStartedListener)} instead */ + @Deprecated public void setOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { cameraChangeDispatcher.setOnCameraMoveStartedListener(listener); } /** + * Adds a callback that is invoked when camera movement has started. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.addOnCameraMoveStartedListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has started. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.removeOnCameraMoveStartedListener(listener); + } + + /** * Sets a callback that is invoked when camera position changes. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveListener(OnCameraMoveListener)} and + * {@link #removeOnCameraMoveListener(OnCameraMoveListener)}instead */ + @Deprecated public void setOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { cameraChangeDispatcher.setOnCameraMoveListener(listener); } /** + * Adds a callback that is invoked when camera position changes. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.addOnCameraMoveListener(listener); + } + + /** + * Removes a callback that is invoked when camera position changes. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.removeOnCameraMoveListener(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. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java index 072382ce07..2c2f07a112 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MarkerContainer.java @@ -98,15 +98,8 @@ class MarkerContainer implements Markers { @NonNull @Override public List<Marker> obtainAllIn(@NonNull RectF rectangle) { - // convert Rectangle to be density depedent - float pixelRatio = nativeMapView.getPixelRatio(); - RectF rect = new RectF(rectangle.left / pixelRatio, - rectangle.top / pixelRatio, - rectangle.right / pixelRatio, - rectangle.bottom / pixelRatio); - + RectF rect = nativeMapView.getDensityDependantRectangle(rectangle); long[] ids = nativeMapView.queryPointAnnotations(rect); - List<Long> idsList = new ArrayList<>(ids.length); for (long id : ids) { idsList.add(id); 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 bd8a54783e..8b6bce69e2 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 @@ -4,6 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; +import android.os.AsyncTask; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -34,7 +35,9 @@ import com.mapbox.services.commons.geojson.Geometry; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import timber.log.Timber; @@ -468,6 +471,13 @@ final class NativeMapView { return nativeQueryPointAnnotations(rect); } + public long[] queryShapeAnnotations(RectF rectF) { + if (isDestroyedOn("queryShapeAnnotations")) { + return new long[] {}; + } + return nativeQueryShapeAnnotations(rectF); + } + public void addAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels) { if (isDestroyedOn("addAnnotationIcon")) { return; @@ -746,6 +756,7 @@ final class NativeMapView { if (isDestroyedOn("addImage")) { return; } + // Check/correct config if (image.getConfig() != Bitmap.Config.ARGB_8888) { image = image.copy(Bitmap.Config.ARGB_8888, false); @@ -762,6 +773,14 @@ final class NativeMapView { nativeAddImage(name, image.getWidth(), image.getHeight(), pixelRatio, buffer.array()); } + public void addImages(@NonNull HashMap<String, Bitmap> bitmapHashMap) { + if (isDestroyedOn("addImages")) { + return; + } + //noinspection unchecked + new BitmapImageConversionTask(this).execute(bitmapHashMap); + } + public void removeImage(String name) { if (isDestroyedOn("removeImage")) { return; @@ -825,6 +844,15 @@ final class NativeMapView { return pixelRatio; } + RectF getDensityDependantRectangle(final RectF rectangle) { + return new RectF( + rectangle.left / pixelRatio, + rectangle.top / pixelRatio, + rectangle.right / pixelRatio, + rectangle.bottom / pixelRatio + ); + } + // // Callbacks // @@ -927,6 +955,8 @@ final class NativeMapView { private native long[] nativeQueryPointAnnotations(RectF rect); + private native long[] nativeQueryShapeAnnotations(RectF rect); + private native void nativeAddAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels); private native void nativeRemoveAnnotationIcon(String symbol); @@ -1006,6 +1036,8 @@ final class NativeMapView { private native void nativeAddImage(String name, int width, int height, float pixelRatio, byte[] array); + private native void nativeAddImages(Image[] images); + private native void nativeRemoveImage(String name); private native Bitmap nativeGetImage(String name); @@ -1093,4 +1125,55 @@ final class NativeMapView { }); } + + + // + // Image conversion + // + + private static class BitmapImageConversionTask extends AsyncTask<HashMap<String, Bitmap>, Void, List<Image>> { + + private NativeMapView nativeMapView; + + BitmapImageConversionTask(NativeMapView nativeMapView) { + this.nativeMapView = nativeMapView; + } + + @Override + protected List<Image> doInBackground(HashMap<String, Bitmap>... params) { + HashMap<String, Bitmap> bitmapHashMap = params[0]; + + List<Image> images = new ArrayList<>(); + ByteBuffer buffer; + String name; + Bitmap bitmap; + + for (Map.Entry<String, Bitmap> stringBitmapEntry : bitmapHashMap.entrySet()) { + name = stringBitmapEntry.getKey(); + bitmap = stringBitmapEntry.getValue(); + + if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); + } + + buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + + float density = bitmap.getDensity() == Bitmap.DENSITY_NONE ? Bitmap.DENSITY_NONE : bitmap.getDensity(); + float pixelRatio = density / DisplayMetrics.DENSITY_DEFAULT; + + images.add(new Image(buffer.array(), pixelRatio, name, bitmap.getWidth(), bitmap.getHeight())); + } + + return images; + } + + @Override + protected void onPostExecute(List<Image> images) { + super.onPostExecute(images); + if (nativeMapView != null && !nativeMapView.isDestroyedOn("nativeAddImages")) { + nativeMapView.nativeAddImages(images.toArray(new Image[images.size()])); + } + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java new file mode 100644 index 0000000000..6ded2f32fb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotationContainer.java @@ -0,0 +1,38 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.RectF; +import android.support.v4.util.LongSparseArray; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.ArrayList; +import java.util.List; + +class ShapeAnnotationContainer implements ShapeAnnotations { + + private final NativeMapView nativeMapView; + private final LongSparseArray<Annotation> annotations; + + ShapeAnnotationContainer(NativeMapView nativeMapView, LongSparseArray<Annotation> annotations) { + this.nativeMapView = nativeMapView; + this.annotations = annotations; + } + + @Override + public List<Annotation> obtainAllIn(RectF rectangle) { + RectF rect = nativeMapView.getDensityDependantRectangle(rectangle); + long[] annotationIds = nativeMapView.queryShapeAnnotations(rect); + return getAnnotationsFromIds(annotationIds); + } + + private List<Annotation> getAnnotationsFromIds(long[] annotationIds) { + List<Annotation> shapeAnnotations = new ArrayList<>(); + for (long annotationId : annotationIds) { + Annotation annotation = annotations.get(annotationId); + if (annotation != null) { + shapeAnnotations.add(annotation); + } + } + return shapeAnnotations; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java new file mode 100644 index 0000000000..f9b2ca10db --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/ShapeAnnotations.java @@ -0,0 +1,13 @@ +package com.mapbox.mapboxsdk.maps; + +import android.graphics.RectF; + +import com.mapbox.mapboxsdk.annotations.Annotation; + +import java.util.List; + +interface ShapeAnnotations { + + List<Annotation> obtainAllIn(RectF rectF); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java index 6f63c2eba8..0366e50627 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java @@ -98,9 +98,6 @@ final class Transform implements MapView.OnMapChangedListener { cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(OnCameraMoveStartedListener.REASON_API_ANIMATION); mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); - if (callback != null) { - callback.onFinish(); - } cameraChangeDispatcher.onCameraIdle(); } } @@ -210,6 +207,10 @@ final class Transform implements MapView.OnMapChangedListener { return cameraPosition.zoom; } + double getRawZoom() { + return mapView.getZoom(); + } + void zoom(boolean zoomIn, @NonNull PointF focalPoint) { CameraPosition cameraPosition = invalidateCameraPosition(); if (cameraPosition != null) { @@ -221,6 +222,17 @@ final class Transform implements MapView.OnMapChangedListener { } } + void zoom(double zoomAddition, @NonNull PointF focalPoint, long duration) { + CameraPosition cameraPosition = invalidateCameraPosition(); + if (cameraPosition != null) { + int newZoom = (int) Math.round(cameraPosition.zoom + zoomAddition); + setZoom(newZoom, focalPoint, duration); + } else { + // we are not transforming, notify about being idle + cameraChangeDispatcher.onCameraIdle(); + } + } + void setZoom(double zoom, @NonNull PointF focalPoint) { setZoom(zoom, focalPoint, 0); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 45f72af1c5..1e604c9bef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -6,10 +6,10 @@ import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; -import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -22,7 +22,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; * use {@link com.mapbox.mapboxsdk.maps.UiSettings}. * </p> */ -public final class CompassView extends AppCompatImageView implements Runnable { +public final class CompassView extends ImageView implements Runnable { public static final long TIME_WAIT_IDLE = 500; public static final long TIME_MAP_NORTH_ANIMATION = 150; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index f210729037..1b9a156352 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -300,10 +300,19 @@ public class OfflineRegion { /** * Pause or resume downloading of regional resources. + * <p> + * After a download has been completed, you are required to reset the state of the region to STATE_INACTIVE. + * </p> * * @param state the download state */ public void setDownloadState(@DownloadState int state) { + if (state == STATE_ACTIVE) { + fileSource.activate(); + } else { + fileSource.deactivate(); + } + this.state = state; setOfflineRegionDownloadState(state); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 5206606c83..5deedc3e63 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -4,6 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Matrix; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -262,35 +263,59 @@ public class MapSnapshotter { nativeCancel(); } - protected void addOverlay(Bitmap original) { + /** + * Draw an overlay on the map snapshot. + * + * @param mapSnapshot the map snapshot to draw the overlay on + */ + protected void addOverlay(MapSnapshot mapSnapshot) { + Bitmap original = mapSnapshot.getBitmap(); + Canvas canvas = new Canvas(original); + addLogo(canvas, original); + } + + /** + * Draw a logo on the canvas created from the map snapshot. + * + * @param canvas the canvas to draw the bitmap on + * @param original the map snapshot image + */ + private void addLogo(Canvas canvas, Bitmap original) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); float margin = displayMetrics.density * LOGO_MARGIN_DP; - Canvas canvas = new Canvas(original); + Bitmap logo = createScaledLogo(original); + canvas.drawBitmap(logo, margin, original.getHeight() - logo.getHeight() - margin, null); + } + + /** + * Create a scaled logo for a map snapshot. + * + * @param snapshot the map snapshot where the logo should be placed on + * @return the scaled bitmap logo + */ + private Bitmap createScaledLogo(Bitmap snapshot) { + Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); + float scale = calculateLogoScale(snapshot, logo); + Matrix matrix = new Matrix(); + matrix.postScale(scale, scale); + return Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true); + } - // Decode just the boundaries - BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); - bitmapOptions.inJustDecodeBounds = true; - BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, bitmapOptions); - int srcWidth = bitmapOptions.outWidth; - int srcHeight = bitmapOptions.outHeight; - - // Ratio, preferred dimensions and resulting sample size - float widthRatio = displayMetrics.widthPixels / original.getWidth(); - float heightRatio = displayMetrics.heightPixels / original.getHeight(); - float prefWidth = srcWidth / widthRatio; - float prefHeight = srcHeight / heightRatio; - int sampleSize = MapSnaphotUtil.calculateInSampleSize(bitmapOptions, (int) prefWidth, (int) prefHeight); - - // Scale bitmap - bitmapOptions.inJustDecodeBounds = false; - bitmapOptions.inScaled = true; - bitmapOptions.inSampleSize = sampleSize; - bitmapOptions.inDensity = srcWidth; - bitmapOptions.inTargetDensity = (int) prefWidth * bitmapOptions.inSampleSize; - Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, bitmapOptions); - - float scaledHeight = bitmapOptions.outHeight * heightRatio / bitmapOptions.inSampleSize; - canvas.drawBitmap(logo, margin, original.getHeight() - scaledHeight - margin, null); + /** + * Calculates the scale of the logo, only allow downscaling. + * + * @param snapshot the bitmap of the map snapshot + * @param logo the bitmap of the mapbox logo + * @return the scale value + */ + private float calculateLogoScale(Bitmap snapshot, Bitmap logo) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + float widthRatio = displayMetrics.widthPixels / snapshot.getWidth(); + float heightRatio = displayMetrics.heightPixels / snapshot.getHeight(); + float prefWidth = logo.getWidth() / widthRatio; + float prefHeight = logo.getHeight() / heightRatio; + float calculatedScale = Math.min(prefWidth / logo.getWidth(), prefHeight / logo.getHeight()) * 2; + return calculatedScale < 1 ? calculatedScale : 1.0f; } /** @@ -302,7 +327,7 @@ public class MapSnapshotter { protected void onSnapshotReady(MapSnapshot snapshot) { if (callback != null) { if (snapshot.isShowLogo()) { - addOverlay(snapshot.getBitmap()); + addOverlay(snapshot); } callback.onSnapshotReady(snapshot); reset(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index a968cdf192..41dc449b50 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -72,7 +72,7 @@ public class FileSource { MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL, MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL); } catch (PackageManager.NameNotFoundException exception) { - Timber.e(exception,"Failed to read the package metadata: "); + Timber.e(exception, "Failed to read the package metadata: "); } catch (Exception exception) { Timber.e(exception, "Failed to read the storage key: "); } @@ -119,17 +119,39 @@ public class FileSource { } private long nativePtr; + private long activeCounter; + private boolean wasPaused; private FileSource(String cachePath, AssetManager assetManager) { initialize(Mapbox.getAccessToken(), cachePath, assetManager); } + public void activate() { + activeCounter++; + if (activeCounter == 1 && wasPaused) { + wasPaused = false; + resume(); + } + } + + public void deactivate() { + activeCounter--; + if (activeCounter == 0) { + wasPaused = true; + pause(); + } + } + public native void setAccessToken(@NonNull String accessToken); public native String getAccessToken(); public native void setApiBaseUrl(String baseUrl); + private native void resume(); + + private native void pause(); + /** * Sets a callback for transforming URLs requested from the internet * <p> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java index 14b18b00dc..1c0e439afc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java @@ -5,6 +5,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.graphics.drawable.DrawableCompat; @@ -30,10 +31,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimary, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimary", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -44,10 +50,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryDarkColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimaryDark", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -58,10 +69,15 @@ public class ColorUtils { */ @ColorInt public static int getAccentColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorAccent, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorAccent", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_gray); + } } /** @@ -122,4 +138,12 @@ public class ColorUtils { throw new ConversionException("Not a valid rgb/rgba value"); } } + + private static int getColorCompat(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getResources().getColor(id, context.getTheme()); + } else { + return context.getResources().getColor(id); + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values-bg/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..262e94f368 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values-bg/strings.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="mapbox_compassContentDescription">Компас на картата. Активирай, за да насочиш картата на север.</string> + <string name="mapbox_attributionsIconContentDescription">Иконка функции. Активирай, за да покажеш диалог функции.</string> + <string name="mapbox_myLocationViewContentDescription">Изглед локация. Това показва местоположението ти на картата.</string> + <string name="mapbox_mapActionDescription">Показва карта създадена с Mapbox. Скролни с два пръста. Мащабирай с два пръста.</string> + <string name="mapbox_attributionsDialogTitle">Mapbox Android SDK</string> + <string name="mapbox_attributionTelemetryTitle">Направи Mapbox картите по-добри.</string> + <string name="mapbox_attributionTelemetryMessage">Помагаш OpenStreetMap и Mapbox картите да станат по-добри, като предоставяш анонимни данни за потребление.</string> + <string name="mapbox_attributionTelemetryPositive">Съгласявам се</string> + <string name="mapbox_attributionTelemetryNegative">Не се съгласявам</string> + <string name="mapbox_attributionTelemetryNeutral">Повече инфо</string> + <string name="mapbox_offline_error_region_definition_invalid">Предоставените OfflineRegionDefinition не пасват в границите на света: %s</string> + + </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values-hu/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..520edb2733 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values-hu/strings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="mapbox_myLocationViewContentDescription">Hely nézet. Megmutatja, hol vagy a térképen.</string> + <string name="mapbox_mapActionDescription">Egy Mapbox-szal készült térkép megjelenítése. Húzd két ujjadat a görgetéshez. Csippentsd össze a nagyításhoz.</string> + <string name="mapbox_attributionsDialogTitle">Mapbox Android SDK</string> + <string name="mapbox_attributionTelemetryTitle">Tedd jobbá a Mapbox térképeket</string> + <string name="mapbox_attributionTelemetryMessage">Segítesz az OpenStreetMap és Mapbox térképek jobbá tételében névtelen felhasználási adatok elküldésével.</string> + <string name="mapbox_attributionTelemetryPositive">Egyetértek</string> + <string name="mapbox_attributionTelemetryNegative">Nem értek egyet</string> + <string name="mapbox_attributionTelemetryNeutral">Több infó</string> + <string name="mapbox_offline_error_region_definition_invalid">A megadott OfflineRegionDefinition nem fér bele a világ kereteibe: %s</string> + + </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml deleted file mode 100644 index eba1fb3a7d..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <style name="mapbox_AlertDialogStyle" parent="Theme.AppCompat.Light.Dialog.Alert"/> - -</resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java index 8d9a360714..bb96c9939d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java @@ -74,15 +74,6 @@ public class LatLngBoundsTest { } @Test - public void emptySpan() { - latLngBounds = new LatLngBounds.Builder() - .include(LAT_LNG_NOT_NULL_ISLAND) - .include(LAT_LNG_NOT_NULL_ISLAND) - .build(); - assertTrue("Should be empty", latLngBounds.isEmptySpan()); - } - - @Test public void notEmptySpan() { latLngBounds = new LatLngBounds.Builder() .include(LAT_LNG_NOT_NULL_ISLAND) diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java index 0d592f9bb3..239ad7392b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/AnnotationManagerTest.java @@ -10,6 +10,7 @@ import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.geometry.LatLng; import org.junit.Test; +import org.mockito.ArgumentMatchers; import java.util.ArrayList; import java.util.List; @@ -32,8 +33,9 @@ public class AnnotationManagerTest { Markers markers = new MarkerContainer(aNativeMapView, aMapView, annotationsArray, aIconManager, aMarkerViewManager); Polygons polygons = new PolygonContainer(aNativeMapView, annotationsArray); Polylines polylines = new PolylineContainer(aNativeMapView, annotationsArray); + ShapeAnnotations shapeAnnotations = new ShapeAnnotationContainer(aNativeMapView, annotationsArray); AnnotationManager annotationManager = new AnnotationManager(aNativeMapView, aMapView, annotationsArray, - aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines); + aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines, shapeAnnotations); Marker aMarker = mock(Marker.class); long aId = 5L; when(aNativeMapView.addMarker(aMarker)).thenReturn(aId); @@ -58,18 +60,23 @@ public class AnnotationManagerTest { Markers markers = new MarkerContainer(aNativeMapView, aMapView, annotationsArray, aIconManager, aMarkerViewManager); Polygons polygons = new PolygonContainer(aNativeMapView, annotationsArray); Polylines polylines = new PolylineContainer(aNativeMapView, annotationsArray); + ShapeAnnotations shapeAnnotations = new ShapeAnnotationContainer(aNativeMapView, annotationsArray); AnnotationManager annotationManager = new AnnotationManager(aNativeMapView, aMapView, annotationsArray, - aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines); + aMarkerViewManager, aIconManager, annotations, markers, polygons, polylines, shapeAnnotations); long firstId = 1L; long secondId = 2L; List<BaseMarkerOptions> markerList = new ArrayList<>(); MarkerOptions firstMarkerOption = new MarkerOptions().position(new LatLng()).title("first"); MarkerOptions secondMarkerOption = new MarkerOptions().position(new LatLng()).title("second"); + markerList.add(firstMarkerOption); markerList.add(secondMarkerOption); MapboxMap aMapboxMap = mock(MapboxMap.class); when(aNativeMapView.addMarker(any(Marker.class))).thenReturn(firstId, secondId); + when(aNativeMapView.addMarkers(ArgumentMatchers.<Marker>anyList())) + .thenReturn(new long[]{firstId, secondId}); + annotationManager.addMarkers(markerList, aMapboxMap); assertEquals(2, annotationManager.getAnnotations().size()); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java new file mode 100644 index 0000000000..090d274fe7 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java @@ -0,0 +1,87 @@ +package com.mapbox.mapboxsdk.maps; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CameraChangeDispatcherTest { + + @Test + public void testSetCameraIdleListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraIdleListener listener = mock(MapboxMap.OnCameraIdleListener.class); + dispatcher.setOnCameraIdleListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraIdle(); + verify(listener).onCameraIdle(); + } + + @Test + public void testSetCameraMoveStartedListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveStartedListener listener = mock(MapboxMap.OnCameraMoveStartedListener.class); + dispatcher.setOnCameraMoveStartedListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + verify(listener).onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + } + + @Test + public void testSetCameraMoveCancelListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveCanceledListener listener = mock(MapboxMap.OnCameraMoveCanceledListener.class); + dispatcher.setOnCameraMoveCanceledListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMoveCanceled(); + verify(listener).onCameraMoveCanceled(); + } + + @Test + public void testSetCameraMoveListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveListener listener = mock(MapboxMap.OnCameraMoveListener.class); + dispatcher.setOnCameraMoveListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMove(); + verify(listener).onCameraMove(); + } + + @Test + public void testAddCameraIdleListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraIdleListener listener = mock(MapboxMap.OnCameraIdleListener.class); + dispatcher.addOnCameraIdleListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraIdle(); + verify(listener).onCameraIdle(); + } + + @Test + public void testAddCameraMoveStartedListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveStartedListener listener = mock(MapboxMap.OnCameraMoveStartedListener.class); + dispatcher.addOnCameraMoveStartedListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + verify(listener).onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + } + + @Test + public void testAddCameraMoveCancelListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveCanceledListener listener = mock(MapboxMap.OnCameraMoveCanceledListener.class); + dispatcher.addOnCameraMoveCancelListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMoveCanceled(); + verify(listener).onCameraMoveCanceled(); + } + + @Test + public void testAddCameraMoveListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveListener listener = mock(MapboxMap.OnCameraMoveListener.class); + dispatcher.addOnCameraMoveListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMove(); + verify(listener).onCameraMove(); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle index 81ee635cf1..f45ad3dc3a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle +++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle @@ -59,6 +59,7 @@ dependencies { // Support libraries compile rootProject.ext.dep.supportAppcompatV7 compile rootProject.ext.dep.supportRecyclerView + compile rootProject.ext.dep.supportDesign // Leak Canary debugCompile rootProject.ext.dep.leakCanaryDebug @@ -71,7 +72,6 @@ dependencies { compile rootProject.ext.dep.lost // Testing dependencies - androidTestCompile rootProject.ext.dep.testSpoonRunner androidTestCompile rootProject.ext.dep.supportAnnotations androidTestCompile rootProject.ext.dep.testRunner androidTestCompile rootProject.ext.dep.testRules @@ -81,8 +81,6 @@ dependencies { apply from: 'gradle-make.gradle' apply from: 'gradle-config.gradle' -apply from: 'gradle-device-farm.gradle' -apply from: 'gradle-spoon.gradle' apply from: 'gradle-checkstyle.gradle' apply from: '../gradle-lint.gradle' diff --git a/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle b/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle deleted file mode 100644 index 5cb5d75bbb..0000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle +++ /dev/null @@ -1,43 +0,0 @@ -apply plugin: 'devicefarm' - -def getAccessKeyDeviceFarm() { - if (project.hasProperty('AWS_ACCESS_KEY_ID_DEVICE_FARM')) { - return AWS_ACCESS_KEY_ID_DEVICE_FARM - } else { - println("Could not locate AWS_ACCESS_KEY_ID_DEVICE_FARM in gradle.properties") - return "" - } -} - -def getSecretAccessKeyDeviceFarm() { - if (project.hasProperty('AWS_SECRET_ACCESS_KEY_DEVICE_FARM')) { - return AWS_SECRET_ACCESS_KEY_DEVICE_FARM - } else { - println("Could not locate AWS_SECRET_ACCESS_KEY_DEVICE_FARM in gradle.properties") - return "" - } -} - -devicefarm { - - projectName "Mapbox GL Android" // required: Must already exists. - devicePool "sanity" // optional: Defaults to "Top Devices" - - authentication { - accessKey getAccessKeyDeviceFarm() - secretKey getSecretAccessKeyDeviceFarm() - } - - devicestate { - wifi "on" - bluetooth "off" - gps "on" - nfc "on" - latitude 47.6204 // default - longitude - 122.3491 // default - } - - instrumentation { - - } -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle b/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle deleted file mode 100644 index 7a5b966443..0000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle +++ /dev/null @@ -1,8 +0,0 @@ -println "configuring spoon" -apply plugin: 'spoon' -spoon { - // Spoon: distributes instrumentation tests to all your Androids - // for more options see https://github.com/stanfy/spoon-gradle-plugin - grantAllPermissions = true - debug = true -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java index 294d57bce1..3dbbaceb1a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java @@ -14,6 +14,7 @@ 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.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.exceptions.InvalidMarkerPositionException; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -31,9 +32,12 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; +import timber.log.Timber; + import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static com.mapbox.mapboxsdk.testapp.utils.TestConstants.LAT_LNG_DELTA; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.assertEquals; @@ -90,9 +94,34 @@ public class MapboxMapTest extends BaseActivityTest { } // - // CameraForLatLngBounds + // Camera tests // @Test + public void testCameraPositionOnFinish() { + ViewUtils.checkViewIsDisplayed(R.id.mapView); + onView(withId(R.id.mapView)).perform(new MapboxMapAction(new InvokeViewAction() { + @Override + public void onViewAction(UiController uiController, View view) { + + final LatLng latLng = new LatLng(30.0, 30.0); + mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(latLng), new MapboxMap.CancelableCallback() { + @Override + public void onCancel() { + } + + @Override + public void onFinish() { + LatLng cameraPositionLatLng = mapboxMap.getCameraPosition().target; + Timber.d(cameraPositionLatLng.toString()); + assertEquals(cameraPositionLatLng.getLatitude(), latLng.getLatitude(), LAT_LNG_DELTA); + assertEquals(cameraPositionLatLng.getLongitude(), latLng.getLongitude(), LAT_LNG_DELTA); + } + }); + } + })); + } + + @Test public void testCameraForLatLngBounds() { ViewUtils.checkViewIsDisplayed(R.id.mapView); onView(withId(R.id.mapView)).perform(new MapboxMapAction(new InvokeViewAction() { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java index c029bc09c4..61bff1f113 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/BaseActivityTest.java @@ -49,8 +49,9 @@ public abstract class BaseActivityTest { throw new RuntimeException("Could not start test for " + getActivityClass().getSimpleName() + ".\n" + "The ViewHierarchy doesn't contain a view with resource id = R.id.mapView or \n" + "the Activity doesn't contain an instance variable with a name equal to mapboxMap.\n" - + "You can resolve this issue be implementing the requirements above or\n add " - + getActivityClass().getSimpleName() + " to the excludeActivities array in `generate-test-code.js`.\n"); + + "You can resolve this issue by adding the requirements above or\n add " + + getActivityClass().getSimpleName() + " to the platform/android/scripts/exclude-activity-gen.json to blacklist" + + " the Activity from being generated.\n"); } } @@ -67,8 +68,7 @@ public abstract class BaseActivityTest { protected abstract Class getActivityClass(); protected void checkViewIsDisplayed(int id) { - onView(withId(id)) - .check(matches(isDisplayed())); + onView(withId(id)).check(matches(isDisplayed())); } protected void waitLoop() { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/AttributionTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/AttributionTest.java index d37c6db2d5..a20426c29c 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/AttributionTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/widgets/AttributionTest.java @@ -21,6 +21,7 @@ import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static android.support.test.espresso.Espresso.onData; @@ -67,6 +68,7 @@ public class AttributionTest extends BaseActivityTest { } @Test + @Ignore public void testMapboxStreetsMapboxAttributionLink() { validateTestSetup(); if (urlSpans == null) { @@ -87,6 +89,7 @@ public class AttributionTest extends BaseActivityTest { } @Test + @Ignore public void testMapboxStreetsOpenStreetMapAttributionLink() { validateTestSetup(); if (urlSpans == null) { @@ -107,6 +110,7 @@ public class AttributionTest extends BaseActivityTest { } @Test + @Ignore public void testImproveMapLink() { validateTestSetup(); if (urlSpans == null) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/utils/OnMapReadyIdlingResource.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/utils/OnMapReadyIdlingResource.java index 1c4981ca5e..0e2e4587ee 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/utils/OnMapReadyIdlingResource.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/utils/OnMapReadyIdlingResource.java @@ -13,10 +13,8 @@ public class OnMapReadyIdlingResource implements IdlingResource, OnMapReadyCallb private MapboxMap mapboxMap; private IdlingResource.ResourceCallback resourceCallback; - private Activity activity; public OnMapReadyIdlingResource(Activity activity) { - this.activity = activity; try { Field field = activity.getClass().getDeclaredField("mapView"); field.setAccessible(true); @@ -24,7 +22,6 @@ public class OnMapReadyIdlingResource implements IdlingResource, OnMapReadyCallb } catch (Exception err) { throw new RuntimeException(err); } - } @Override @@ -53,4 +50,4 @@ public class OnMapReadyIdlingResource implements IdlingResource, OnMapReadyCallb resourceCallback.onTransitionToIdle(); } } -} +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java index cc44ac9715..c8c5c6bd37 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java @@ -5,13 +5,20 @@ import android.animation.AnimatorSet; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.os.Bundle; +import android.support.v4.util.LongSparseArray; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.support.v4.view.animation.PathInterpolatorCompat; import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.BounceInterpolator; +import android.view.animation.Interpolator; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -24,6 +31,51 @@ import com.mapbox.mapboxsdk.testapp.R; public class CameraAnimatorActivity extends AppCompatActivity implements OnMapReadyCallback { private static final double ANIMATION_DELAY_FACTOR = 1.5; + private static final LatLng START_LAT_LNG = new LatLng(37.787947, -122.407432); + + private final LongSparseArray<AnimatorBuilder> animators = new LongSparseArray<AnimatorBuilder>() { + { + put(R.id.menu_action_accelerate_decelerate_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + createLatLngAnimator(START_LAT_LNG, new LatLng(37.826715, -122.422795)), + obtainExampleInterpolator(new FastOutSlowInInterpolator(), 2500) + ); + return animatorSet; + } + }); + + put(R.id.menu_action_bounce_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + createLatLngAnimator(START_LAT_LNG, new LatLng(37.787947, -122.407432)), + obtainExampleInterpolator(new BounceInterpolator(), 3750) + ); + return animatorSet; + } + }); + + put(R.id.menu_action_anticipate_overshoot_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + return obtainExampleInterpolator(new AnticipateOvershootInterpolator(), 2500); + } + }); + + put(R.id.menu_action_path_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + return obtainExampleInterpolator( + PathInterpolatorCompat.create(.22f, .68f, 0, 1.71f), 2500); + } + }); + } + }; + private MapView mapView; private MapboxMap mapboxMap; @@ -32,7 +84,6 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera_animator); - mapView = (MapView) findViewById(R.id.mapView); if (mapView != null) { mapView.onCreate(savedInstanceState); @@ -43,27 +94,42 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe @Override public void onMapReady(final MapboxMap map) { mapboxMap = map; + initFab(); + } + + private void initFab() { findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { view.setVisibility(View.GONE); - createAnimator(mapboxMap.getCameraPosition()).start(); + + CameraPosition animatedPosition = new CameraPosition.Builder() + .target(new LatLng(37.789992, -122.402214)) + .tilt(60) + .zoom(14.5f) + .bearing(135) + .build(); + + createExampleAnimator(mapboxMap.getCameraPosition(), animatedPosition).start(); } }); } - private Animator createAnimator(CameraPosition currentPosition) { + // + // Animator API used for the animation on the FAB + // + + private Animator createExampleAnimator(CameraPosition currentPosition, CameraPosition targetPosition) { AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.play(createLatLngAnimator(currentPosition.target)); - animatorSet.play(createZoomAnimator(currentPosition.zoom)); - animatorSet.play(createBearingAnimator(currentPosition.bearing)); - animatorSet.play(createTiltAnimator(currentPosition.tilt)); + animatorSet.play(createLatLngAnimator(currentPosition.target, targetPosition.target)); + animatorSet.play(createZoomAnimator(currentPosition.zoom, targetPosition.zoom)); + animatorSet.play(createBearingAnimator(currentPosition.bearing, targetPosition.bearing)); + animatorSet.play(createTiltAnimator(currentPosition.tilt, targetPosition.tilt)); return animatorSet; } - private Animator createLatLngAnimator(LatLng currentPosition) { - LatLng target = new LatLng(37.789992, -122.402214); - ValueAnimator latLngAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), currentPosition, target); + private Animator createLatLngAnimator(LatLng currentPosition, LatLng targetPosition) { + ValueAnimator latLngAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), currentPosition, targetPosition); latLngAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); latLngAnimator.setInterpolator(new FastOutSlowInInterpolator()); latLngAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -75,8 +141,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return latLngAnimator; } - private Animator createZoomAnimator(double currentZoom) { - ValueAnimator zoomAnimator = ValueAnimator.ofFloat((float) currentZoom, 14.5f); + private Animator createZoomAnimator(double currentZoom, double targetZoom) { + ValueAnimator zoomAnimator = ValueAnimator.ofFloat((float) currentZoom, (float) targetZoom); zoomAnimator.setDuration((long) (2200 * ANIMATION_DELAY_FACTOR)); zoomAnimator.setStartDelay((long) (600 * ANIMATION_DELAY_FACTOR)); zoomAnimator.setInterpolator(new AnticipateOvershootInterpolator()); @@ -89,8 +155,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return zoomAnimator; } - private Animator createBearingAnimator(double currentBearing) { - ValueAnimator bearingAnimator = ValueAnimator.ofFloat((float) currentBearing, 135); + private Animator createBearingAnimator(double currentBearing, double targetBearing) { + ValueAnimator bearingAnimator = ValueAnimator.ofFloat((float) currentBearing, (float) targetBearing); bearingAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); bearingAnimator.setStartDelay((long) (1000 * ANIMATION_DELAY_FACTOR)); bearingAnimator.setInterpolator(new FastOutLinearInInterpolator()); @@ -103,8 +169,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return bearingAnimator; } - private Animator createTiltAnimator(double currentTilt) { - ValueAnimator tiltAnimator = ValueAnimator.ofFloat((float) currentTilt, 60); + private Animator createTiltAnimator(double currentTilt, double targetTilt) { + ValueAnimator tiltAnimator = ValueAnimator.ofFloat((float) currentTilt, (float) targetTilt); tiltAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); tiltAnimator.setStartDelay((long) (1500 * ANIMATION_DELAY_FACTOR)); tiltAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -116,20 +182,66 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return tiltAnimator; } - private static class LatLngEvaluator implements TypeEvaluator<LatLng> { + // + // Interpolator examples + // - private final LatLng latLng = new LatLng(); + private Animator obtainExampleInterpolator(int menuItemId) { + return animators.get(menuItemId).build(); + } - @Override - public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) { - latLng.setLatitude(startValue.getLatitude() - + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); - latLng.setLongitude(startValue.getLongitude() - + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); - return latLng; + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_animator, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mapboxMap == null) { + return false; } + findViewById(R.id.fab).setVisibility(View.GONE); + resetCameraPosition(); + playAnimation(item.getItemId()); + return super.onOptionsItemSelected(item); } + private void resetCameraPosition() { + mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition( + new CameraPosition.Builder() + .target(START_LAT_LNG) + .zoom(11) + .bearing(0) + .tilt(0) + .build() + )); + } + + private void playAnimation(int itemId) { + Animator animator = obtainExampleInterpolator(itemId); + if (animator != null) { + animator.start(); + } + } + + private Animator obtainExampleInterpolator(Interpolator interpolator, long duration) { + ValueAnimator zoomAnimator = ValueAnimator.ofFloat(11.0f, 16.0f); + zoomAnimator.setDuration((long) (duration * ANIMATION_DELAY_FACTOR)); + zoomAnimator.setInterpolator(interpolator); + zoomAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mapboxMap.setZoom((Float) animation.getAnimatedValue()); + } + }); + return zoomAnimator; + } + + // + // MapView lifecycle + // + @Override protected void onStart() { super.onStart(); @@ -171,4 +283,25 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe super.onLowMemory(); mapView.onLowMemory(); } + + /** + * Helper class to evaluate LatLng objects with a ValueAnimator + */ + private static class LatLngEvaluator implements TypeEvaluator<LatLng> { + + private final LatLng latLng = new LatLng(); + + @Override + public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) { + latLng.setLatitude(startValue.getLatitude() + + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); + latLng.setLongitude(startValue.getLongitude() + + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); + return latLng; + } + } + + interface AnimatorBuilder { + Animator build(); + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java index 2820bdbd53..e5c6a3f584 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java @@ -1,6 +1,5 @@ package com.mapbox.mapboxsdk.testapp.activity.camera; -import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; @@ -28,11 +27,13 @@ import timber.log.Timber; /** * Test activity showcasing how to listen to camera change events. */ -public class CameraPositionActivity extends AppCompatActivity implements OnMapReadyCallback { +public class CameraPositionActivity extends AppCompatActivity implements OnMapReadyCallback, View.OnClickListener, + MapboxMap.OnMapLongClickListener { private MapView mapView; private MapboxMap mapboxMap; private FloatingActionButton fab; + private boolean logCameraChanges; @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,96 +48,48 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe @Override public void onMapReady(@NonNull final MapboxMap map) { mapboxMap = map; - - mapboxMap.setOnCameraIdleListener(new MapboxMap.OnCameraIdleListener() { - @Override - public void onCameraIdle() { - Timber.e("OnCameraIdle"); - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_green_dark)); - } - }); - - mapboxMap.setOnCameraMoveCancelListener(new MapboxMap.OnCameraMoveCanceledListener() { - @Override - public void onCameraMoveCanceled() { - Timber.e("OnCameraMoveCanceled"); - } - }); - - mapboxMap.setOnCameraMoveListener(new MapboxMap.OnCameraMoveListener() { - @Override - public void onCameraMove() { - Timber.e("OnCameraMove"); - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_orange_dark)); - } - }); - - mapboxMap.setOnCameraMoveStartedListener(new MapboxMap.OnCameraMoveStartedListener() { - - private final String[] REASONS = {"REASON_API_GESTURE", "REASON_DEVELOPER_ANIMATION", "REASON_API_ANIMATION"}; - - @Override - public void onCameraMoveStarted(int reason) { - // reason ranges from 1 <-> 3 - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_red_dark)); - Timber.e("OnCameraMoveStarted: %s", REASONS[reason - 1]); - } - }); + toggleLogCameraChanges(); // add a listener to FAB fab = (FloatingActionButton) findViewById(R.id.fab); fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, R.color.primary)); - fab.setOnClickListener(new View.OnClickListener() { - @SuppressLint("InflateParams") - @Override - public void onClick(View view) { - Context context = view.getContext(); - final View dialogContent = LayoutInflater.from(context).inflate(R.layout.dialog_camera_position, null); - AlertDialog.Builder builder = new AlertDialog.Builder( - context, com.mapbox.mapboxsdk.R.style.mapbox_AlertDialogStyle); - builder.setTitle(R.string.dialog_camera_position); - builder.setView(onInflateDialogContent(dialogContent)); - builder.setPositiveButton("Animate", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - double latitude = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_lat)).getText().toString()); - double longitude = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_lon)).getText().toString()); - double zoom = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_zoom)).getText().toString()); - double bearing = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_bearing)).getText().toString()); - double tilt = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_tilt)).getText().toString()); - - CameraPosition cameraPosition = new CameraPosition.Builder() - .target(new LatLng(latitude, longitude)) - .zoom(zoom) - .bearing(bearing) - .tilt(tilt) - .build(); - - mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 5000, - new MapboxMap.CancelableCallback() { - @Override - public void onCancel() { - Timber.v("OnCancel called"); - } - - @Override - public void onFinish() { - Timber.v("OnFinish called"); - } - }); - Timber.v(cameraPosition.toString()); - } - }); - builder.setNegativeButton("Cancel", null); - builder.setCancelable(false); - builder.show(); - } - }); + fab.setOnClickListener(this); + + // listen to long click events to toggle logging camera changes + mapboxMap.setOnMapLongClickListener(this); + } + + @Override + public void onMapLongClick(@NonNull LatLng point) { + toggleLogCameraChanges(); + } + + @Override + public void onClick(View view) { + Context context = view.getContext(); + final View dialogContent = LayoutInflater.from(context).inflate(R.layout.dialog_camera_position, null); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.dialog_camera_position); + builder.setView(onInflateDialogContent(dialogContent)); + builder.setPositiveButton("Animate", new DialogClickListener(mapboxMap, dialogContent)); + builder.setNegativeButton("Cancel", null); + builder.setCancelable(false); + builder.show(); + } + + private void toggleLogCameraChanges() { + logCameraChanges = !logCameraChanges; + if (logCameraChanges) { + mapboxMap.addOnCameraIdleListener(idleListener); + mapboxMap.addOnCameraMoveCancelListener(moveCanceledListener); + mapboxMap.addOnCameraMoveListener(moveListener); + mapboxMap.addOnCameraMoveStartedListener(moveStartedListener); + } else { + mapboxMap.removeOnCameraIdleListener(idleListener); + mapboxMap.removeOnCameraMoveCancelListener(moveCanceledListener); + mapboxMap.removeOnCameraMoveListener(moveListener); + mapboxMap.removeOnCameraMoveStartedListener(moveStartedListener); + } } @Override @@ -193,6 +146,42 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe seekBar.setProgress(defaultValue); } + private MapboxMap.OnCameraIdleListener idleListener = new MapboxMap.OnCameraIdleListener() { + @Override + public void onCameraIdle() { + Timber.e("OnCameraIdle"); + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_green_dark)); + } + }; + + private MapboxMap.OnCameraMoveListener moveListener = new MapboxMap.OnCameraMoveListener() { + @Override + public void onCameraMove() { + Timber.e("OnCameraMove"); + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_orange_dark)); + } + }; + + private MapboxMap.OnCameraMoveCanceledListener moveCanceledListener = new MapboxMap.OnCameraMoveCanceledListener() { + @Override + public void onCameraMoveCanceled() { + Timber.e("OnCameraMoveCanceled"); + + } + }; + + private MapboxMap.OnCameraMoveStartedListener moveStartedListener = new MapboxMap.OnCameraMoveStartedListener() { + + private final String[] REASONS = {"REASON_API_GESTURE", "REASON_DEVELOPER_ANIMATION", "REASON_API_ANIMATION"}; + + @Override + public void onCameraMoveStarted(int reason) { + // reason ranges from 1 <-> 3 + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_red_dark)); + Timber.e("OnCameraMoveStarted: %s", REASONS[reason - 1]); + } + }; + private class ValueChangeListener implements SeekBar.OnSeekBarChangeListener { protected TextView textView; @@ -224,4 +213,50 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe super.onProgressChanged(seekBar, progress - 180, fromUser); } } + + private static class DialogClickListener implements DialogInterface.OnClickListener { + + private MapboxMap mapboxMap; + private View dialogContent; + + public DialogClickListener(MapboxMap mapboxMap, View view) { + this.mapboxMap = mapboxMap; + this.dialogContent = view; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + double latitude = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_lat)).getText().toString()); + double longitude = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_lon)).getText().toString()); + double zoom = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_zoom)).getText().toString()); + double bearing = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_bearing)).getText().toString()); + double tilt = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_tilt)).getText().toString()); + + CameraPosition cameraPosition = new CameraPosition.Builder() + .target(new LatLng(latitude, longitude)) + .zoom(zoom) + .bearing(bearing) + .tilt(tilt) + .build(); + + mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 5000, + new MapboxMap.CancelableCallback() { + @Override + public void onCancel() { + Timber.v("OnCancel called"); + } + + @Override + public void onFinish() { + Timber.v("OnFinish called"); + } + }); + Timber.v(cameraPosition.toString()); + } + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java index 5bffd4d930..3a59e0628d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java @@ -264,6 +264,7 @@ public class OfflineActivity extends AppCompatActivity if (status.isComplete()) { // Download complete endProgress("Region downloaded successfully."); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); offlineRegion.setObserver(null); return; } else if (status.isRequiredResourceCountPrecise()) { @@ -281,11 +282,13 @@ public class OfflineActivity extends AppCompatActivity @Override public void onError(OfflineRegionError error) { Timber.e("onError: %s, %s", error.getReason(), error.getMessage()); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); } @Override public void mapboxTileCountLimitExceeded(long limit) { Timber.e("Mapbox tile count limit exceeded: %s", limit); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); } }); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java index 0eaccfef0c..6e9e45cfaa 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java @@ -1,13 +1,14 @@ package com.mapbox.mapboxsdk.testapp.activity.style; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.v7.app.AppCompatActivity; import android.graphics.Color; import android.graphics.PointF; +import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; - +import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -32,7 +33,7 @@ import com.mapbox.services.commons.geojson.custom.PositionDeserializer; import com.mapbox.services.commons.models.Position; import java.io.IOException; - +import java.util.HashMap; import java.util.List; import timber.log.Timber; @@ -64,49 +65,10 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR } @Override - public void onMapReady(MapboxMap map) { + public void onMapReady(final MapboxMap map) { mapboxMap = map; - try { - // read local geojson from raw folder - String tinyCountriesJson = ResourceUtils.readRawResource(this, R.raw.tiny_countries); - - // convert geojson to a model - FeatureCollection featureCollection = new GsonBuilder() - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) - .registerTypeAdapter(Position.class, new PositionDeserializer()) - .create().fromJson(tinyCountriesJson, FeatureCollection.class); - - // add a geojson to the map - Source source = new GeoJsonSource(SOURCE_ID, featureCollection); - mapboxMap.addSource(source); - - // for each feature add a symbolLayer - for (Feature feature : featureCollection.getFeatures()) { - String countryName = feature.getStringProperty(FEATURE_ID); - - // create View - TextView textView = new TextView(this); - textView.setBackgroundColor(getResources().getColor(R.color.blueAccent)); - textView.setPadding(10, 5, 10, 5); - textView.setTextColor(Color.WHITE); - textView.setText(countryName); - - // create bitmap from view - mapboxMap.addImage(countryName, SymbolGenerator.generate(textView)); - } - - // create layer use - mapboxMap.addLayer(new SymbolLayer(LAYER_ID, SOURCE_ID) - .withProperties( - iconImage("{" + FEATURE_ID + "}"), // { } is a token notation - iconAllowOverlap(false) - ) - ); - - addSymbolClickListener(); - } catch (IOException exception) { - Timber.e(exception); - } + addSymbolClickListener(); + new LoadDataTask(map, SymbolGeneratorActivity.this).execute(); } private void addSymbolClickListener() { @@ -213,4 +175,91 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR return bitmap; } } -} + + private static class LoadDataTask extends AsyncTask<Void, Void, FeatureCollection> { + + private final MapboxMap mapboxMap; + private final Context context; + + LoadDataTask(MapboxMap mapboxMap, Context context) { + this.mapboxMap = mapboxMap; + this.context = context; + } + + @Override + protected FeatureCollection doInBackground(Void... params) { + try { + // read local geojson from raw folder + String tinyCountriesJson = ResourceUtils.readRawResource(context, R.raw.tiny_countries); + + // convert geojson to a model + FeatureCollection featureCollection = new GsonBuilder() + .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) + .registerTypeAdapter(Position.class, new PositionDeserializer()) + .create().fromJson(tinyCountriesJson, FeatureCollection.class); + + return featureCollection; + } catch (IOException exception) { + return null; + } + } + + + @Override + protected void onPostExecute(FeatureCollection featureCollection) { + super.onPostExecute(featureCollection); + if (featureCollection == null) { + return; + } + + // add a geojson to the map + Source source = new GeoJsonSource(SOURCE_ID, featureCollection); + mapboxMap.addSource(source); + + // create layer use + mapboxMap.addLayer(new SymbolLayer(LAYER_ID, SOURCE_ID) + .withProperties( + iconImage("{" + FEATURE_ID + "}"), // { } is a token notation + iconAllowOverlap(false) + ) + ); + + new GenerateSymbolTask(mapboxMap, context).execute(featureCollection); + } + } + + private static class GenerateSymbolTask extends AsyncTask<FeatureCollection, Void, HashMap<String, Bitmap>> { + + private MapboxMap mapboxMap; + private Context context; + + GenerateSymbolTask(MapboxMap mapboxMap, Context context) { + this.mapboxMap = mapboxMap; + this.context = context; + } + + @SuppressWarnings("WrongThread") + @Override + protected HashMap<String, Bitmap> doInBackground(FeatureCollection... params) { + FeatureCollection featureCollection = params[0]; + + HashMap<String, Bitmap> imagesMap = new HashMap<>(); + for (Feature feature : featureCollection.getFeatures()) { + String countryName = feature.getStringProperty(FEATURE_ID); + TextView textView = new TextView(context); + textView.setBackgroundColor(context.getResources().getColor(R.color.blueAccent)); + textView.setPadding(10, 5, 10, 5); + textView.setTextColor(Color.WHITE); + textView.setText(countryName); + imagesMap.put(countryName, SymbolGenerator.generate(textView)); + } + return imagesMap; + } + + @Override + protected void onPostExecute(HashMap<String, Bitmap> bitmapHashMap) { + super.onPostExecute(bitmapHashMap); + mapboxMap.addImages(bitmapHashMap); + } + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java index 95c3929c1d..abfd7ae529 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/ZoomFunctionSymbolLayerActivity.java @@ -12,6 +12,7 @@ import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.style.layers.Property; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.testapp.R; @@ -32,6 +33,7 @@ import static com.mapbox.mapboxsdk.style.functions.stops.Stops.interval; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility; /** * Test activity showcasing changing the icon with a zoom function and adding selection state to a SymbolLayer. @@ -49,9 +51,11 @@ public class ZoomFunctionSymbolLayerActivity extends AppCompatActivity { private MapView mapView; private MapboxMap mapboxMap; private GeoJsonSource source; + private SymbolLayer layer; private boolean isInitialPosition = true; private boolean isSelected = false; + private boolean isShowingSymbolLayer = true; @Override public void onCreate(Bundle savedInstanceState) { @@ -81,6 +85,13 @@ public class ZoomFunctionSymbolLayerActivity extends AppCompatActivity { } } + private void toggleSymbolLayerVisibility() { + layer.setProperties( + visibility(isShowingSymbolLayer ? Property.NONE : Property.VISIBLE) + ); + isShowingSymbolLayer = !isShowingSymbolLayer; + } + private FeatureCollection createFeatureCollection() { Position position = isInitialPosition ? Position.fromCoordinates(-74.01618140, 40.701745) @@ -95,7 +106,7 @@ public class ZoomFunctionSymbolLayerActivity extends AppCompatActivity { } private void addLayer() { - SymbolLayer layer = new SymbolLayer(LAYER_ID, SOURCE_ID); + layer = new SymbolLayer(LAYER_ID, SOURCE_ID); layer.setProperties( iconImage( zoom( @@ -145,9 +156,13 @@ public class ZoomFunctionSymbolLayerActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { - if (mapboxMap != null && item.getItemId() == R.id.menu_action_change_location) { - isInitialPosition = !isInitialPosition; - updateSource(); + if (mapboxMap != null) { + if (item.getItemId() == R.id.menu_action_change_location) { + isInitialPosition = !isInitialPosition; + updateSource(); + } else if (item.getItemId() == R.id.menu_action_toggle_source) { + toggleSymbolLayerVisibility(); + } } return super.onOptionsItemSelected(item); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml index d4933bfb9a..cb14aab91f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml @@ -10,8 +10,8 @@ android:id="@id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" - app:mapbox_cameraTargetLat="37.774913" - app:mapbox_cameraTargetLng="-122.419368" + app:mapbox_cameraTargetLat="37.787947" + app:mapbox_cameraTargetLng="-122.407432" app:mapbox_cameraZoom="11" app:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml new file mode 100644 index 0000000000..db5a62d2cb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/menu_action_accelerate_decelerate_interpolator" + android:title="@string/menuitem_title_accelerate_decelerate" + app:showAsAction="never"/> + <item + android:id="@+id/menu_action_bounce_interpolator" + android:title="@string/menuitem_title_bounce" + app:showAsAction="never"/> + <item + android:id="@+id/menu_action_anticipate_overshoot_interpolator" + android:title="@string/menuitem_title_anticipate_overshoot" + app:showAsAction="never"/> + <item + android:id="@+id/menu_action_path_interpolator" + android:title="@string/menuitem_title_path" + app:showAsAction="never"/> +</menu> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_symbols.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_symbols.xml index 3e5c8ab14c..7f3c44262d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_symbols.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_symbols.xml @@ -5,4 +5,8 @@ android:id="@+id/menu_action_change_location" android:title="@string/menuitem_change_location" app:showAsAction="never"/> + <item + android:id="@+id/menu_action_toggle_source" + android:title="@string/menuitem_toggle_symbol_layer_visibility" + app:showAsAction="never"/> </menu> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml index 4ca19def71..5c0828ab74 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml @@ -12,6 +12,11 @@ <string name="menuitem_title_change_location_source_null">Reset location source to null</string> <string name="menuitem_change_icon_overlap">Toggle icon overlap</string> <string name="menuitem_change_location">Change location</string> + <string name="menuitem_title_accelerate_decelerate">Accelerate/Decelerate interpolator</string> + <string name="menuitem_title_bounce">Bounce interpolator</string> + <string name="menuitem_title_anticipate_overshoot">Anticipate/Overshoot interpolator</string> + <string name="menuitem_title_path">PathInterpolator interpolator</string> + <string name="menuitem_toggle_symbol_layer_visibility">Toggle Symbol Layer Visibility</string> <string name="button_camera_move">Move</string> <string name="button_camera_ease">Ease</string> <string name="button_camera_animate">Animate</string> diff --git a/platform/android/build.gradle b/platform/android/build.gradle index e298b84da8..f3301d0b5a 100644 --- a/platform/android/build.gradle +++ b/platform/android/build.gradle @@ -4,8 +4,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.3.1' - classpath 'com.amazonaws:aws-devicefarm-gradle-plugin:1.2' - classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.1' } } diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 4af7a4293d..b3888a9418 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -142,6 +142,8 @@ add_library(mbgl-android STATIC platform/android/src/style/conversion/types_string_values.hpp platform/android/src/map/camera_position.cpp platform/android/src/map/camera_position.hpp + platform/android/src/map/image.cpp + platform/android/src/map/image.hpp # Style conversion Java -> C++ platform/android/src/style/android_conversion.hpp diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index 650cf38952..8d4c32045c 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -27,7 +27,6 @@ ext { mockito : 'org.mockito:mockito-core:2.10.0', // instrumentation test - testSpoonRunner : 'com.squareup.spoon:spoon-client:1.7.1', testRunner : "com.android.support.test:runner:${testRunnerVersion}", testRules : "com.android.support.test:rules:${testRunnerVersion}", testEspressoCore : "com.android.support.test.espresso:espresso-core:${espressoVersion}", @@ -36,7 +35,7 @@ ext { // support supportAnnotations : "com.android.support:support-annotations:${supportLibVersion}", supportAppcompatV7 : "com.android.support:appcompat-v7:${supportLibVersion}", - supportV4 : "com.android.support:support-v4:${supportLibVersion}", + supportFragmentV4 : "com.android.support:support-fragment:${supportLibVersion}", supportDesign : "com.android.support:design:${supportLibVersion}", supportRecyclerView : "com.android.support:recyclerview-v7:${supportLibVersion}", diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json new file mode 100644 index 0000000000..f05001c6ae --- /dev/null +++ b/platform/android/scripts/exclude-activity-gen.json @@ -0,0 +1,31 @@ +[ + "BaseLocationActivity", + "MapSnapshotterMarkerActivity", + "MapSnapshotterReuseActivity", + "LatLngBoundsActivity", + "BottomSheetActivity", + "MapSnapshotterActivity", + "MockLocationEngine", + "DeleteRegionActivity", + "RealTimeGeoJsonActivity", + "UpdateMetadataActivity", + "CarDrivingActivity", + "MyLocationTrackingModeActivity", + "MyLocationToggleActivity", + "MyLocationTintActivity", + "MyLocationDrawableActivity", + "DoubleMapActivity", + "LocationPickerActivity", + "GeoJsonClusteringActivity", + "RuntimeStyleTestActivity", + "AnimatedMarkerActivity", + "ViewPagerActivity", + "MapFragmentActivity", + "SupportMapFragmentActivity", + "SnapshotActivity", + "NavigationDrawerActivity", + "QueryRenderedFeaturesBoxHighlightActivity", + "MultiMapActivity", + "MapInDialogActivity", + "SimpleMapActivity" +]
\ No newline at end of file diff --git a/platform/android/scripts/generate-test-code.js b/platform/android/scripts/generate-test-code.js index 01a294a301..e27de7e40b 100644 --- a/platform/android/scripts/generate-test-code.js +++ b/platform/android/scripts/generate-test-code.js @@ -13,8 +13,7 @@ global.camelize = function (str) { }); } - -const excludeActivities = ["BaseLocationActivity","MockLocationEngine","DeleteRegionActivity","RealTimeGeoJsonActivity","UpdateMetadataActivity","CarDrivingActivity","MyLocationTrackingModeActivity","MyLocationToggleActivity","MyLocationTintActivity","MyLocationDrawableActivity","DoubleMapActivity", "LocationPickerActivity","GeoJsonClusteringActivity","RuntimeStyleTestActivity", "AnimatedMarkerActivity", "ViewPagerActivity","MapFragmentActivity","SupportMapFragmentActivity","SnapshotActivity","NavigationDrawerActivity", "QueryRenderedFeaturesBoxHighlightActivity", "MultiMapActivity", "MapInDialogActivity", "SimpleMapActivity"]; +const excludeClasses = JSON.parse(fs.readFileSync('platform/android/scripts/exclude-activity-gen.json', 'utf8')); const appBasePath = 'platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity'; const testBasePath = 'platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/gen'; const subPackages = fs.readdirSync(appBasePath); @@ -24,7 +23,9 @@ if (!fs.existsSync(testBasePath)){ fs.mkdirSync(testBasePath); } -console.log("Generating test activities:"); +console.log("\nGenerating test activities:\n"); +var generatedClasses = []; +var excludedClasses = []; for(const subPackage of subPackages) { if(!(subPackage.slice(-5) == '.java')) { const activities = fs.readdirSync(appBasePath+'/'+subPackage); @@ -45,18 +46,22 @@ for(const subPackage of subPackages) { try { fs.accessSync(filePath, fs.F_OK); fs.unlinkSync(filePath); - console.log("Removed file: "+filePath); } catch (e) { - console.log("No file found: "+filePath); } - // only generate test file if not part of exclude list - if (!(excludeActivities.indexOf(activityName) > -1)) { - console.log("Created file: "+filePath); + // only generate test file if not part of exclude list + if contains Activity in name + if ((!(excludeClasses.indexOf(activityName) > -1)) && activityName.includes("Activity")) { fs.writeFileSync(filePath, ejsConversionTask([activityName, subPackage])); + generatedClasses.push(activityName); }else{ - console.log("Excluding file: "+filePath); + excludedClasses.push(activityName); } } } } + +for(const generatedClass of generatedClasses){ + console.log(generatedClass+"Test"); +} + +console.log("\nFinished generating " + generatedClasses.length + " activity sanity tests, excluded " + excludeClasses.length + " classes.\n");
\ No newline at end of file diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index b80e23e21f..afdb08a10e 100644 --- a/platform/android/src/android_renderer_frontend.cpp +++ b/platform/android/src/android_renderer_frontend.cpp @@ -112,6 +112,11 @@ AnnotationIDs AndroidRendererFrontend::queryPointAnnotations(const ScreenBox& bo return mapRenderer.actor().ask(&Renderer::queryPointAnnotations, box).get(); } +AnnotationIDs AndroidRendererFrontend::queryShapeAnnotations(const ScreenBox& box) const { + // Waits for the result from the orchestration thread and returns + return mapRenderer.actor().ask(&Renderer::queryShapeAnnotations, box).get(); +} + } // namespace android } // namespace mbgl diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp index 94508fd816..178870c452 100644 --- a/platform/android/src/android_renderer_frontend.hpp +++ b/platform/android/src/android_renderer_frontend.hpp @@ -36,6 +36,7 @@ public: std::vector<Feature> queryRenderedFeatures(const ScreenBox&, const RenderedQueryOptions&) const; std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; AnnotationIDs queryPointAnnotations(const ScreenBox& box) const; + AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const; // Memory void onLowMemory(); diff --git a/platform/android/src/file_source.cpp b/platform/android/src/file_source.cpp index 262e3d3c6a..a576661a4f 100644 --- a/platform/android/src/file_source.cpp +++ b/platform/android/src/file_source.cpp @@ -8,8 +8,6 @@ #include "asset_manager_file_source.hpp" #include "jni/generic_global_ref_deleter.hpp" -#include <string> - namespace mbgl { namespace android { @@ -64,6 +62,14 @@ void FileSource::setResourceTransform(jni::JNIEnv& env, jni::Object<FileSource:: } } +void FileSource::resume(jni::JNIEnv&) { + fileSource->resume(); +} + +void FileSource::pause(jni::JNIEnv&) { + fileSource->pause(); +} + jni::Class<FileSource> FileSource::javaClass; FileSource* FileSource::getNativePeer(jni::JNIEnv& env, jni::Object<FileSource> jFileSource) { @@ -93,7 +99,9 @@ void FileSource::registerNative(jni::JNIEnv& env) { METHOD(&FileSource::getAccessToken, "getAccessToken"), METHOD(&FileSource::setAccessToken, "setAccessToken"), METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"), - METHOD(&FileSource::setResourceTransform, "setResourceTransform") + METHOD(&FileSource::setResourceTransform, "setResourceTransform"), + METHOD(&FileSource::resume, "resume"), + METHOD(&FileSource::pause, "pause") ); } diff --git a/platform/android/src/file_source.hpp b/platform/android/src/file_source.hpp index 4abe352bff..2933aedf86 100644 --- a/platform/android/src/file_source.hpp +++ b/platform/android/src/file_source.hpp @@ -41,6 +41,10 @@ public: void setResourceTransform(jni::JNIEnv&, jni::Object<FileSource::ResourceTransformCallback>); + void resume(jni::JNIEnv&); + + void pause(jni::JNIEnv&); + static jni::Class<FileSource> javaClass; static FileSource* getNativePeer(jni::JNIEnv&, jni::Object<FileSource>); diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index f39aeb6374..f4e5734861 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -172,6 +172,7 @@ void registerNatives(JavaVM *vm) { // Map CameraPosition::registerNative(env); + Image::registerNative(env); // Connectivity ConnectivityListener::registerNative(env); diff --git a/platform/android/src/map/image.cpp b/platform/android/src/map/image.cpp new file mode 100644 index 0000000000..5f5c90eddd --- /dev/null +++ b/platform/android/src/map/image.cpp @@ -0,0 +1,44 @@ +#include <mbgl/style/image.hpp> +#include <mbgl/util/exception.hpp> +#include "image.hpp" + +namespace mbgl { +namespace android { + +mbgl::style::Image Image::getImage(jni::JNIEnv& env, jni::Object<Image> image) { + static auto widthField = Image::javaClass.GetField<jni::jint>(env, "width"); + static auto heightField = Image::javaClass.GetField<jni::jint>(env, "height"); + static auto pixelRatioField = Image::javaClass.GetField<jni::jfloat>(env, "pixelRatio"); + static auto bufferField = Image::javaClass.GetField<jni::Array<jbyte>>(env, "buffer"); + static auto nameField = Image::javaClass.GetField<jni::String>(env, "name"); + + auto height = image.Get(env, heightField); + auto width = image.Get(env, widthField); + auto pixelRatio = image.Get(env, pixelRatioField); + auto pixels = image.Get(env, bufferField); + auto name = jni::Make<std::string>(env, image.Get(env, nameField)); + + jni::NullCheck(env, &pixels); + std::size_t size = pixels.Length(env); + + mbgl::PremultipliedImage premultipliedImage({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) }); + if (premultipliedImage.bytes() != uint32_t(size)) { + throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); + } + + jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast<jbyte*>(premultipliedImage.data.get())); + + return mbgl::style::Image {name, std::move(premultipliedImage), pixelRatio}; +} + +void Image::registerNative(jni::JNIEnv &env) { + // Lookup the class + Image::javaClass = *jni::Class<Image>::Find(env).NewGlobalRef(env).release(); +} + +jni::Class<Image> Image::javaClass; + + +} // namespace android +} // namespace mb + diff --git a/platform/android/src/map/image.hpp b/platform/android/src/map/image.hpp new file mode 100644 index 0000000000..1513e13ee7 --- /dev/null +++ b/platform/android/src/map/image.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <mbgl/util/noncopyable.hpp> + +#include <jni/jni.hpp> +#include <mbgl/style/image.hpp> + +namespace mbgl { +namespace android { + +class Image : private mbgl::util::noncopyable { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/maps/Image"; }; + + static mbgl::style::Image getImage(jni::JNIEnv&, jni::Object<Image>); + + static jni::Class<Image> javaClass; + + static void registerNative(jni::JNIEnv&); + +}; + + +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 24a35a7068..04cbb21927 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -49,6 +49,7 @@ #include "java/util.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" +#include "map/image.hpp" #include "style/light.hpp" #include "bitmap_factory.hpp" @@ -633,6 +634,26 @@ jni::Array<jlong> NativeMapView::queryPointAnnotations(JNIEnv& env, jni::Object< return result; } +jni::Array<jlong> NativeMapView::queryShapeAnnotations(JNIEnv &env, jni::Object<RectF> rect) { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + + // Convert input + mbgl::ScreenBox box = { + {RectF::getLeft(env, rect), RectF::getTop(env, rect)}, + {RectF::getRight(env, rect), RectF::getBottom(env, rect)}, + }; + + mbgl::AnnotationIDs ids = rendererFrontend->queryShapeAnnotations(box); + + // Convert result + std::vector<jlong> longIds(ids.begin(), ids.end()); + auto result = jni::Array<jni::jlong>::New(env, ids.size()); + result.SetRegion<std::vector<jni::jlong>>(env, 0, longIds); + + return result; +} + jni::Array<jni::Object<geojson::Feature>> NativeMapView::queryRenderedFeaturesForPoint(JNIEnv& env, jni::jfloat x, jni::jfloat y, jni::Array<jni::String> layerIds, jni::Array<jni::Object<>> jfilter) { @@ -904,6 +925,18 @@ void NativeMapView::addImage(JNIEnv& env, jni::String name, jni::jint w, jni::ji float(scale))); } +void NativeMapView::addImages(JNIEnv& env, jni::Array<jni::Object<mbgl::android::Image>> jimages) { + jni::NullCheck(env, &jimages); + std::size_t len = jimages.Length(env); + + for (std::size_t i = 0; i < len; i++) { + jni::Object<mbgl::android::Image> jimage = jimages.Get(env, i); + auto image = mbgl::android::Image::getImage(env, jimage); + map->getStyle().addImage(std::make_unique<mbgl::style::Image>(image)); + jni::DeleteLocalRef(env, jimage); + } +} + void NativeMapView::removeImage(JNIEnv& env, jni::String name) { map->getStyle().removeImage(jni::Make<std::string>(env, name)); } @@ -1000,6 +1033,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getTransitionDelay, "nativeGetTransitionDelay"), METHOD(&NativeMapView::setTransitionDelay, "nativeSetTransitionDelay"), METHOD(&NativeMapView::queryPointAnnotations, "nativeQueryPointAnnotations"), + METHOD(&NativeMapView::queryShapeAnnotations, "nativeQueryShapeAnnotations"), METHOD(&NativeMapView::queryRenderedFeaturesForPoint, "nativeQueryRenderedFeaturesForPoint"), METHOD(&NativeMapView::queryRenderedFeaturesForBox, "nativeQueryRenderedFeaturesForBox"), METHOD(&NativeMapView::getLight, "nativeGetLight"), @@ -1017,6 +1051,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::removeSourceById, "nativeRemoveSourceById"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), + METHOD(&NativeMapView::addImages, "nativeAddImages"), METHOD(&NativeMapView::removeImage, "nativeRemoveImage"), METHOD(&NativeMapView::getImage, "nativeGetImage"), METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 4d226d0fa9..d7e3b17b99 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -22,6 +22,7 @@ #include "style/sources/sources.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" +#include "map/image.hpp" #include "style/light.hpp" #include "bitmap.hpp" @@ -197,6 +198,8 @@ public: jni::Array<jlong> queryPointAnnotations(JNIEnv&, jni::Object<RectF>); + jni::Array<jlong> queryShapeAnnotations(JNIEnv&, jni::Object<RectF>); + jni::Array<jni::Object<geojson::Feature>> queryRenderedFeaturesForPoint(JNIEnv&, jni::jfloat, jni::jfloat, jni::Array<jni::String>, jni::Array<jni::Object<>> jfilter); @@ -235,6 +238,8 @@ public: void addImage(JNIEnv&, jni::String, jni::jint, jni::jint, jni::jfloat, jni::Array<jbyte>); + void addImages(JNIEnv&, jni::Array<jni::Object<mbgl::android::Image>>); + void removeImage(JNIEnv&, jni::String); jni::Object<Bitmap> getImage(JNIEnv&, jni::String); |