diff options
author | kevin <kevin.li@mapbox.com> | 2019-01-16 08:29:58 -0800 |
---|---|---|
committer | kevin <kevin.li@mapbox.com> | 2019-01-16 08:29:58 -0800 |
commit | c09831cda04bf97c1c208cacfaaa660c36003cbd (patch) | |
tree | e1ff23c5feaea81d0e6ec096b17e5f223204c2d4 | |
parent | 788158cc5608bc428b13ad0ef6215626e1acaffd (diff) | |
parent | b23a6122259cac97f174dddb978437dd3a58a5df (diff) | |
download | qtlocation-mapboxgl-upstream/no-v7.tar.gz |
Merge branch 'release-horchata' into no-v7upstream/no-v7
52 files changed, 1058 insertions, 94 deletions
diff --git a/appveyor.yml b/appveyor.yml index b1fe0b9f4f..53970e1ac5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,8 +47,8 @@ for: - image: Visual Studio 2017 environment: - LLVM_VERSION: 6.0.0 - LLVM_HASH: 2501887b2f638d3f65b0336f354b96f8108b563522d81e841d5c88c34af283dd + LLVM_VERSION: 7.0.0 + LLVM_HASH: 74b197a3959b0408adf0824be01db8dddfa2f9a967f4085af3fad900ed5fdbf6 VCVARSALL: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat' QT_PREFIX: 'C:\Qt\latest\msvc2017_64\lib\cmake' diff --git a/include/mbgl/map/map_observer.hpp b/include/mbgl/map/map_observer.hpp index f63e5f2af3..98b218f8f0 100644 --- a/include/mbgl/map/map_observer.hpp +++ b/include/mbgl/map/map_observer.hpp @@ -39,6 +39,7 @@ public: virtual void onDidFinishRenderingMap(RenderMode) {} virtual void onDidFinishLoadingStyle() {} virtual void onSourceChanged(style::Source&) {} + virtual void onDidBecomeIdle() {} }; } // namespace mbgl diff --git a/package.json b/package.json index 7ddbad1997..ec8556c5ea 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "license": "BSD-2-Clause", "dependencies": { - "@mapbox/cmake-node-module": "^1.1.0", + "@mapbox/cmake-node-module": "1.1.0", "node-pre-gyp": "^0.10.2", "npm-run-all": "^4.0.2" }, diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 743916dad9..a9a8c7606c 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -3,7 +3,37 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. ## master -- Add `fill-extrusion-vertical-gradient` fill paint property [#13463](https://github.com/mapbox/mapbox-gl-native/pull/13463) + +## 6.8.0 - December 19, 2018 + - Update core styles based on the streets-v8 source. This results in breaking changes for any code that references internal layers and sources of the style [#13615](https://github.com/mapbox/mapbox-gl-native/pull/13615) + - Fixed compass update time not getting updated [#13569](https://github.com/mapbox/mapbox-gl-native/pull/13569) + - Register compass sensor listener only if necessary [#13573](https://github.com/mapbox/mapbox-gl-native/pull/13573) + - Immediate location animation based on map's projection [#13545](https://github.com/mapbox/mapbox-gl-native/pull/13545) + - Option to disable smooth animation of compass and accuracy values [#13574](https://github.com/mapbox/mapbox-gl-native/pull/13574) + - Add onDidBecomeIdle to Map events [#13513](https://github.com/mapbox/mapbox-gl-native/pull/13513) + - Expose setAccessToken on Mapbox.java [#13397](https://github.com/mapbox/mapbox-gl-native/pull/13397) + - Use locale sensitive DecimalFormat for color alpha conversion [#13393](https://github.com/mapbox/mapbox-gl-native/pull/13393) + - Enabled RecyclerView integration [#13132](https://github.com/mapbox/mapbox-gl-native/pull/13132) + +## 7.0.0-beta.1 - December 12, 2018 + - Style builder and Style.java [#13484](https://github.com/mapbox/mapbox-gl-native/pull/13484) + - LatLng bounds cannot be wrapped [#13419](https://github.com/mapbox/mapbox-gl-native/pull/13419) + - Expose endpoint change option for snapshotter [#13508](https://github.com/mapbox/mapbox-gl-native/pull/13508) + - Formatted "text-field" property setter [#13358](https://github.com/mapbox/mapbox-gl-native/pull/13358) + - Expose setAccesToken on Mapbox.java [#13397](https://github.com/mapbox/mapbox-gl-native/pull/13397) + - Immediately notify core about forced connectivity state [#13485](https://github.com/mapbox/mapbox-gl-native/pull/13485) + - Handle transition duration of 0ms for symbols [#13535](https://github.com/mapbox/mapbox-gl-native/pull/13535) + - Returning boolean when removing sources and layers [#13428](https://github.com/mapbox/mapbox-gl-native/pull/13428) + - Telemetry v4.1.1 bump [#13499](https://github.com/mapbox/mapbox-gl-native/pull/13499) + - mapbox-java 4.2.0 bump [#13507](https://github.com/mapbox/mapbox-gl-native/pull/13507) + +## 7.0.0-alpha.3 - November 30, 2018 + - Add `fill-extrusion-vertical-gradient` fill paint property [#13463](https://github.com/mapbox/mapbox-gl-native/pull/13463) + - Don't cancel core transitions on MotionEvent#ACTION_CANCEL [#13465](https://github.com/mapbox/mapbox-gl-native/pull/13465) + - Require context in AttributionParser.Options [#13444](https://github.com/mapbox/mapbox-gl-native/pull/13444) + - Deprecated annotations package [#13468](https://github.com/mapbox/mapbox-gl-native/pull/13468) + - Send turnstile event while create MapSnapshotter instance [#13475](https://github.com/mapbox/mapbox-gl-native/pull/13475) + - Remove default zoom level restrictions when activating the location component [#13425](https://github.com/mapbox/mapbox-gl-native/pull/13425) ## 6.7.2 - November 30, 2018 - Telemetry v3.5.6 [#13486](https://github.com/mapbox/mapbox-gl-native/pull/13486) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java index 3959d2cf3c..4d680f8873 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java @@ -68,7 +68,7 @@ public final class Mapbox { } /** - * Access token for this application. + * Get the current active access token for this application. * * @return Mapbox access token */ @@ -79,6 +79,15 @@ public final class Mapbox { } /** + * Set the current active accessToken. + */ + public static void setAccessToken(String accessToken) { + validateMapbox(); + INSTANCE.accessToken = accessToken; + FileSource.getInstance(getApplicationContext()).setAccessToken(accessToken); + } + + /** * Application context * * @return the application context diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassEngine.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassEngine.java index 3691bdc0ea..33efa06226 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassEngine.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassEngine.java @@ -55,11 +55,17 @@ public interface CompassEngine { /** * Lifecycle method that can be used for adding or releasing resources. + * + * @deprecated Use {@link #addCompassListener(CompassListener)} */ + @Deprecated void onStart(); /** * Lifecycle method that can be used for adding or releasing resources. + * + * @deprecated Use {@link #removeCompassListener(CompassListener)} */ + @Deprecated void onStop(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassListener.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassListener.java index 3e5eb7f258..c44218a1d1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassListener.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/CompassListener.java @@ -7,7 +7,7 @@ public interface CompassListener { /** * Callback's invoked when a new compass update occurs. You can listen into the compass updates - * using {@link LocationComponent#addCompassListener(CompassListener)} and implementing these + * using {@link CompassEngine#addCompassListener(CompassListener)} and implementing these * callbacks. Note that this interface is also used internally to to update the UI chevron/arrow. * * @param userHeading the new compass heading diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java index 0a6c9b5ddc..4ac124ef24 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinator.java @@ -1,7 +1,6 @@ package com.mapbox.mapboxsdk.location; import android.animation.Animator; -import android.animation.AnimatorSet; import android.location.Location; import android.os.SystemClock; import android.support.annotation.NonNull; @@ -13,13 +12,13 @@ import android.view.animation.LinearInterpolator; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; import java.util.ArrayList; import java.util.List; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_RADIUS_ANIMATION_DURATION; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.COMPASS_UPDATE_RATE_MS; -import static com.mapbox.mapboxsdk.location.LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.MAX_ANIMATION_DURATION_MS; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_CAMERA_COMPASS_BEARING; @@ -31,6 +30,7 @@ import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_GPS_BE import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_LATLNG; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_TILT; import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_ZOOM; +import static com.mapbox.mapboxsdk.location.Utils.immediateAnimation; final class LocationAnimatorCoordinator { @@ -39,11 +39,20 @@ final class LocationAnimatorCoordinator { final List<MapboxAnimator.OnLayerAnimationsValuesChangeListener> layerListeners = new ArrayList<>(); final List<MapboxAnimator.OnCameraAnimationsValuesChangeListener> cameraListeners = new ArrayList<>(); + private final Projection projection; private Location previousLocation; private float previousAccuracyRadius = -1; private float previousCompassBearing = -1; private long locationUpdateTimestamp = -1; private float durationMultiplier; + private final MapboxAnimatorSetProvider animatorSetProvider; + private boolean compassAnimationEnabled; + private boolean accuracyAnimationEnabled; + + LocationAnimatorCoordinator(@NonNull Projection projection, @NonNull MapboxAnimatorSetProvider animatorSetProvider) { + this.projection = projection; + this.animatorSetProvider = animatorSetProvider; + } void addLayerListener(MapboxAnimator.OnLayerAnimationsValuesChangeListener listener) { layerListeners.add(listener); @@ -81,8 +90,8 @@ final class LocationAnimatorCoordinator { updateLayerAnimators(previousLayerLatLng, targetLatLng, previousLayerBearing, targetLayerBearing); updateCameraAnimators(previousCameraLatLng, previousCameraBearing, targetLatLng, targetCameraBearing); - boolean snap = immediateAnimation(previousCameraLatLng, targetLatLng, currentCameraPosition.zoom) - || immediateAnimation(previousLayerLatLng, targetLatLng, currentCameraPosition.zoom); + boolean snap = immediateAnimation(projection, previousCameraLatLng, targetLatLng) + || immediateAnimation(projection, previousLayerLatLng, targetLatLng); playLocationAnimators(snap ? 0 : getAnimationDuration()); previousLocation = newLocation; @@ -97,7 +106,7 @@ final class LocationAnimatorCoordinator { float previousCameraBearing = (float) currentCameraPosition.bearing; updateCompassAnimators(targetCompassBearing, previousLayerBearing, previousCameraBearing); - playCompassAnimators(COMPASS_UPDATE_RATE_MS); + playCompassAnimators(compassAnimationEnabled ? COMPASS_UPDATE_RATE_MS : 0); previousCompassBearing = targetCompassBearing; } @@ -109,7 +118,7 @@ final class LocationAnimatorCoordinator { float previousAccuracyRadius = getPreviousAccuracyRadius(); updateAccuracyAnimators(targetAccuracyRadius, previousAccuracyRadius); - playAccuracyAnimator(noAnimation ? 0 : ACCURACY_RADIUS_ANIMATION_DURATION); + playAccuracyAnimator(noAnimation || !accuracyAnimationEnabled ? 0 : ACCURACY_RADIUS_ANIMATION_DURATION); this.previousAccuracyRadius = targetAccuracyRadius; } @@ -249,27 +258,20 @@ final class LocationAnimatorCoordinator { locationAnimators.add(animatorArray.get(ANIMATOR_LAYER_GPS_BEARING)); locationAnimators.add(animatorArray.get(ANIMATOR_CAMERA_LATLNG)); locationAnimators.add(animatorArray.get(ANIMATOR_CAMERA_GPS_BEARING)); - AnimatorSet locationAnimatorSet = new AnimatorSet(); - locationAnimatorSet.playTogether(locationAnimators); - locationAnimatorSet.setInterpolator(new LinearInterpolator()); - locationAnimatorSet.setDuration(duration); - locationAnimatorSet.start(); + animatorSetProvider.startAnimation(locationAnimators, new LinearInterpolator(), duration); } private void playCompassAnimators(long duration) { List<Animator> compassAnimators = new ArrayList<>(); compassAnimators.add(animatorArray.get(ANIMATOR_LAYER_COMPASS_BEARING)); compassAnimators.add(animatorArray.get(ANIMATOR_CAMERA_COMPASS_BEARING)); - AnimatorSet compassAnimatorSet = new AnimatorSet(); - compassAnimatorSet.playTogether(compassAnimators); - compassAnimatorSet.setDuration(duration); - compassAnimatorSet.start(); + animatorSetProvider.startAnimation(compassAnimators, new LinearInterpolator(), duration); } private void playAccuracyAnimator(long duration) { - MapboxAnimator animator = animatorArray.get(ANIMATOR_LAYER_ACCURACY); - animator.setDuration(duration); - animator.start(); + List<Animator> accuracyAnimators = new ArrayList<>(); + accuracyAnimators.add(animatorArray.get(ANIMATOR_LAYER_ACCURACY)); + animatorSetProvider.startAnimation(accuracyAnimators, new LinearInterpolator(), duration); } private void playZoomAnimator(long duration) { @@ -288,11 +290,7 @@ final class LocationAnimatorCoordinator { List<Animator> locationAnimators = new ArrayList<>(); locationAnimators.add(animatorArray.get(ANIMATOR_CAMERA_LATLNG)); locationAnimators.add(animatorArray.get(ANIMATOR_CAMERA_GPS_BEARING)); - AnimatorSet locationAnimatorSet = new AnimatorSet(); - locationAnimatorSet.playTogether(locationAnimators); - locationAnimatorSet.setInterpolator(new FastOutSlowInInterpolator()); - locationAnimatorSet.setDuration(duration); - locationAnimatorSet.start(); + animatorSetProvider.startAnimation(locationAnimators, new FastOutSlowInInterpolator(), duration); } void resetAllCameraAnimations(CameraPosition currentCameraPosition, boolean isGpsNorth) { @@ -317,7 +315,7 @@ final class LocationAnimatorCoordinator { createNewAnimator(ANIMATOR_CAMERA_LATLNG, new CameraLatLngAnimator(previousCameraTarget, currentTarget, cameraListeners)); - return immediateAnimation(previousCameraTarget, currentTarget, currentCameraPosition.zoom); + return immediateAnimation(projection, previousCameraTarget, currentTarget); } private void resetCameraGpsBearingAnimation(CameraPosition currentCameraPosition, boolean isGpsNorth) { @@ -382,12 +380,11 @@ final class LocationAnimatorCoordinator { this.durationMultiplier = trackingAnimationDurationMultiplier; } - private boolean immediateAnimation(LatLng current, LatLng target, double zoom) { - // TODO: calculate the value based on the projection - double distance = current.distanceTo(target); - if (zoom > 10) { - distance *= zoom; - } - return distance > INSTANT_LOCATION_TRANSITION_THRESHOLD; + void setCompassAnimationEnabled(boolean compassAnimationEnabled) { + this.compassAnimationEnabled = compassAnimationEnabled; + } + + void setAccuracyAnimationEnabled(boolean accuracyAnimationEnabled) { + this.accuracyAnimationEnabled = accuracyAnimationEnabled; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java index e3194df009..340ee3d0f2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationCameraController.java @@ -164,6 +164,11 @@ final class LocationCameraController implements MapboxAnimator.OnCameraAnimation } } + boolean isConsumingCompass() { + return cameraMode == CameraMode.TRACKING_COMPASS + || cameraMode == CameraMode.NONE_COMPASS; + } + private boolean isLocationTracking() { return cameraMode == CameraMode.TRACKING || cameraMode == CameraMode.TRACKING_COMPASS diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java index 9118f926f3..e624378487 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java @@ -26,6 +26,8 @@ import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener; +import org.jetbrains.annotations.NotNull; + import java.util.concurrent.CopyOnWriteArrayList; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; @@ -116,6 +118,11 @@ public final class LocationComponent { */ private boolean isLayerReady; + /** + * Indicates whether we are listening for compass updates. + */ + private boolean isListeningToCompass; + private StaleStateManager staleStateManager; private final CopyOnWriteArrayList<OnLocationStaleListener> onLocationStaleListeners = new CopyOnWriteArrayList<>(); @@ -135,6 +142,21 @@ public final class LocationComponent { this.mapboxMap = mapboxMap; } + public LocationComponent(@NotNull MapboxMap mapboxMap, @NotNull LocationEngine locationEngine, + @NotNull LocationLayerController locationLayerController, + @NotNull LocationCameraController locationCameraController, + @NotNull LocationAnimatorCoordinator locationAnimatorCoordinator, + @NotNull StaleStateManager staleStateManager, @NotNull CompassEngine compassEngine) { + this.mapboxMap = mapboxMap; + this.locationEngine = locationEngine; + this.locationLayerController = locationLayerController; + this.locationCameraController = locationCameraController; + this.locationAnimatorCoordinator = locationAnimatorCoordinator; + this.staleStateManager = staleStateManager; + this.compassEngine = compassEngine; + isInitialized = true; + } + /** * This method initializes the component and needs to be called before any other operations are performed. * Afterwards, you can manage component's visibility by {@link #setLocationComponentEnabled(boolean)}. @@ -276,6 +298,7 @@ public final class LocationComponent { locationCameraController.setCameraMode(cameraMode); boolean isGpsNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH; locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), isGpsNorth); + updateCompassListenerState(true); } /** @@ -302,6 +325,7 @@ public final class LocationComponent { public void setRenderMode(@RenderMode.Mode int renderMode) { locationLayerController.setRenderMode(renderMode); updateLayerOffsets(true); + updateCompassListenerState(true); } /** @@ -345,6 +369,8 @@ public final class LocationComponent { staleStateManager.setEnabled(options.enableStaleState()); staleStateManager.setDelayTime(options.staleStateTimeout()); locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options.trackingAnimationDurationMultiplier()); + locationAnimatorCoordinator.setCompassAnimationEnabled(options.compassAnimationEnabled()); + locationAnimatorCoordinator.setAccuracyAnimationEnabled(options.accuracyAnimationEnabled()); updateMapWithOptions(options); } @@ -518,10 +544,12 @@ public final class LocationComponent { * * @param compassEngine to be used */ - public void setCompassEngine(@NonNull CompassEngine compassEngine) { - this.compassEngine.removeCompassListener(compassListener); + public void setCompassEngine(@Nullable CompassEngine compassEngine) { + if (this.compassEngine != null) { + updateCompassListenerState(false); + } this.compassEngine = compassEngine; - compassEngine.addCompassListener(compassListener); + updateCompassListenerState(true); } /** @@ -529,7 +557,7 @@ public final class LocationComponent { * * @return compass engine currently being used */ - @NonNull + @Nullable public CompassEngine getCompassEngine() { return compassEngine; } @@ -555,9 +583,11 @@ public final class LocationComponent { * The last known accuracy of the compass sensor, one of SensorManager.SENSOR_STATUS_* * * @return the last know compass accuracy bearing + * @deprecated Use {@link #getCompassEngine()} */ + @Deprecated public float getLastKnownCompassAccuracyStatus() { - return compassEngine.getLastAccuracySensorStatus(); + return compassEngine != null ? compassEngine.getLastAccuracySensorStatus() : 0; } /** @@ -566,9 +596,13 @@ public final class LocationComponent { * * @param compassListener a {@link CompassListener} for listening into compass heading and * accuracy changes + * @deprecated Use {@link #getCompassEngine()} */ + @Deprecated public void addCompassListener(@NonNull CompassListener compassListener) { - compassEngine.addCompassListener(compassListener); + if (compassEngine != null) { + compassEngine.addCompassListener(compassListener); + } } /** @@ -576,9 +610,13 @@ public final class LocationComponent { * * @param compassListener the {@link CompassListener} which you'd like to remove from the listener * list. + * @deprecated Use {@link #getCompassEngine()} */ + @Deprecated public void removeCompassListener(@NonNull CompassListener compassListener) { - compassEngine.removeCompassListener(compassListener); + if (compassEngine != null) { + compassEngine.removeCompassListener(compassListener); + } } /** @@ -714,7 +752,6 @@ public final class LocationComponent { if (options.enableStaleState()) { staleStateManager.onStart(); } - compassEngine.onStart(); } if (isEnabled) { @@ -726,6 +763,7 @@ public final class LocationComponent { } setCameraMode(locationCameraController.getCameraMode()); setLastLocation(); + updateCompassListenerState(true); setLastCompassHeading(); } } @@ -738,7 +776,9 @@ public final class LocationComponent { isLayerReady = false; locationLayerController.hide(); staleStateManager.onStop(); - compassEngine.onStop(); + if (compassEngine != null) { + updateCompassListenerState(false); + } locationAnimatorCoordinator.cancelAllAnimations(); if (locationEngine != null) { if (usingInternalLocationEngine) { @@ -768,15 +808,19 @@ public final class LocationComponent { locationCameraController = new LocationCameraController( context, mapboxMap, cameraTrackingChangedListener, options, onCameraMoveInvalidateListener); - locationAnimatorCoordinator = new LocationAnimatorCoordinator(); + locationAnimatorCoordinator = new LocationAnimatorCoordinator( + mapboxMap.getProjection(), + MapboxAnimatorSetProvider.getInstance() + ); locationAnimatorCoordinator.addLayerListener(locationLayerController); locationAnimatorCoordinator.addCameraListener(locationCameraController); locationAnimatorCoordinator.setTrackingAnimationDurationMultiplier(options.trackingAnimationDurationMultiplier()); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager); - compassEngine.addCompassListener(compassListener); + if (windowManager != null && sensorManager != null) { + compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager); + } staleStateManager = new StaleStateManager(onLocationStaleListener, options); updateMapWithOptions(options); @@ -804,6 +848,38 @@ public final class LocationComponent { locationEngine.activate(); } + private void updateCompassListenerState(boolean canListen) { + if (compassEngine != null) { + if (!canListen) { + // We shouldn't listen, simply unregistering + removeCompassListener(compassEngine); + return; + } + + if (!isInitialized || !isComponentStarted || !isEnabled) { + return; + } + + if (locationCameraController.isConsumingCompass() || locationLayerController.isConsumingCompass()) { + // If we have a consumer, and not yet listening, then start listening + if (!isListeningToCompass) { + isListeningToCompass = true; + compassEngine.addCompassListener(compassListener); + } + } else { + // If we have no consumers, stop listening + removeCompassListener(compassEngine); + } + } + } + + private void removeCompassListener(@NonNull CompassEngine engine) { + if (isListeningToCompass) { + isListeningToCompass = false; + engine.removeCompassListener(compassListener); + } + } + private void enableLocationComponent() { isEnabled = true; onLocationLayerStart(); @@ -815,9 +891,12 @@ public final class LocationComponent { } private void updateMapWithOptions(final LocationComponentOptions options) { - mapboxMap.setPadding( - options.padding()[0], options.padding()[1], options.padding()[2], options.padding()[3] - ); + int[] padding = options.padding(); + if (padding != null) { + mapboxMap.setPadding( + padding[0], padding[1], padding[2], padding[3] + ); + } mapboxMap.setMaxZoomPreference(options.maxZoom()); mapboxMap.setMinZoomPreference(options.minZoom()); @@ -869,7 +948,7 @@ public final class LocationComponent { } private void setLastCompassHeading() { - updateCompassHeading(compassEngine.getLastHeading()); + updateCompassHeading(compassEngine != null ? compassEngine.getLastHeading() : 0); } @SuppressLint("MissingPermission") diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentCompassEngine.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentCompassEngine.java index b53d909de3..6db307369a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentCompassEngine.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentCompassEngine.java @@ -10,6 +10,8 @@ import android.support.annotation.Nullable; import android.view.Surface; import android.view.WindowManager; +import com.mapbox.mapboxsdk.log.Logger; + import java.util.ArrayList; import java.util.List; @@ -21,15 +23,18 @@ import timber.log.Timber; */ class LocationComponentCompassEngine implements CompassEngine, SensorEventListener { + private static final String TAG = "Mbgl-LocationComponentCompassEngine"; + // The rate sensor events will be delivered at. As the Android documentation states, this is only // a hint to the system and the events might actually be received faster or slower then this // specified rate. Since the minimum Android API levels about 9, we are able to set this value // ourselves rather than using one of the provided constants which deliver updates too quickly for // our use case. The default is set to 100ms - private static final int SENSOR_DELAY_MICROS = 100 * 1000; + static final int SENSOR_DELAY_MICROS = 100 * 1000; // Filtering coefficient 0 < ALPHA < 1 private static final float ALPHA = 0.45f; + @NonNull private final WindowManager windowManager; private final SensorManager sensorManager; private final List<CompassListener> compassListeners = new ArrayList<>(); @@ -56,7 +61,7 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen * Construct a new instance of the this class. A internal compass listeners needed to separate it * from the cleared list of public listeners. */ - LocationComponentCompassEngine(WindowManager windowManager, SensorManager sensorManager) { + LocationComponentCompassEngine(@NonNull WindowManager windowManager, @NonNull SensorManager sensorManager) { this.windowManager = windowManager; this.sensorManager = sensorManager; compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); @@ -75,7 +80,7 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen @Override public void addCompassListener(@NonNull CompassListener compassListener) { if (compassListeners.isEmpty()) { - onStart(); + registerSensorListeners(); } compassListeners.add(compassListener); } @@ -84,7 +89,7 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen public void removeCompassListener(@NonNull CompassListener compassListener) { compassListeners.remove(compassListener); if (compassListeners.isEmpty()) { - onStop(); + unregisterSensorListeners(); } } @@ -100,12 +105,16 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen @Override public void onStart() { - registerSensorListeners(); + if (!compassListeners.isEmpty()) { + registerSensorListeners(); + } } @Override public void onStop() { - unregisterSensorListeners(); + if (!compassListeners.isEmpty()) { + unregisterSensorListeners(); + } } @Override @@ -116,15 +125,12 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen return; } if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) { - Timber.d("Compass sensor is unreliable, device calibration is needed."); + Logger.d(TAG, "Compass sensor is unreliable, device calibration is needed."); return; } if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { rotationVectorValue = getRotationVectorFromSensorEvent(event); updateOrientation(); - - // Update the compassUpdateNextTimestamp - compassUpdateNextTimestamp = currentTime + LocationComponentConstants.COMPASS_UPDATE_RATE_MS; } else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) { notifyCompassChangeListeners((event.values[0] + 360) % 360); } else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { @@ -134,6 +140,9 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues); updateOrientation(); } + + // Update the compassUpdateNextTimestamp + compassUpdateNextTimestamp = currentTime + LocationComponentConstants.COMPASS_UPDATE_RATE_MS; } @Override @@ -233,7 +242,8 @@ class LocationComponentCompassEngine implements CompassEngine, SensorEventListen * @param smoothedValues array of float, that contains previous state * @return float filtered array of float */ - private float[] lowPassFilter(float[] newValues, float[] smoothedValues) { + @NonNull + private float[] lowPassFilter(@NonNull float[] newValues, @Nullable float[] smoothedValues) { if (smoothedValues == null) { return newValues; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java index 4376ef4e60..135534460a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentConstants.java @@ -6,7 +6,7 @@ package com.mapbox.mapboxsdk.location; final class LocationComponentConstants { // Controls the compass update rate in milliseconds - static final int COMPASS_UPDATE_RATE_MS = 500; + static final long COMPASS_UPDATE_RATE_MS = 500; // Sets the transition animation duration when switching camera modes. static final long TRANSITION_ANIMATION_DURATION_MS = 750; @@ -24,7 +24,7 @@ final class LocationComponentConstants { static final long DEFAULT_TRACKING_TILT_ANIM_DURATION = 1250; // Threshold value to perform immediate camera/layer position update. - static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 500_000; + static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 50_000; // Sources static final String LOCATION_SOURCE = "mapbox-location-source"; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java index 10dfff8694..58b86c7b3a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java @@ -111,6 +111,8 @@ public class LocationComponentOptions implements Parcelable { private float trackingMultiFingerMoveThreshold; private String layerBelow; private float trackingAnimationDurationMultiplier; + private boolean compassAnimationEnabled; + private boolean accuracyAnimationEnabled; public LocationComponentOptions( float accuracyAlpha, @@ -144,7 +146,9 @@ public class LocationComponentOptions implements Parcelable { float trackingInitialMoveThreshold, float trackingMultiFingerMoveThreshold, String layerBelow, - float trackingAnimationDurationMultiplier) { + float trackingAnimationDurationMultiplier, + boolean compassAnimationEnabled, + boolean accuracyAnimationEnabled) { this.accuracyAlpha = accuracyAlpha; this.accuracyColor = accuracyColor; this.backgroundDrawableStale = backgroundDrawableStale; @@ -180,6 +184,8 @@ public class LocationComponentOptions implements Parcelable { this.trackingMultiFingerMoveThreshold = trackingMultiFingerMoveThreshold; this.layerBelow = layerBelow; this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier; + this.compassAnimationEnabled = compassAnimationEnabled; + this.accuracyAnimationEnabled = accuracyAnimationEnabled; } /** @@ -304,6 +310,14 @@ public class LocationComponentOptions implements Parcelable { ); builder.trackingAnimationDurationMultiplier(trackingAnimationDurationMultiplier); + builder.compassAnimationEnabled = typedArray.getBoolean( + R.styleable.mapbox_LocationComponent_mapbox_compassAnimationEnabled, true + ); + + builder.accuracyAnimationEnabled = typedArray.getBoolean( + R.styleable.mapbox_LocationComponent_mapbox_accuracyAnimationEnabled, true + ); + typedArray.recycle(); return builder.build(); @@ -723,6 +737,26 @@ public class LocationComponentOptions implements Parcelable { return trackingAnimationDurationMultiplier; } + /** + * Enable or disable smooth animation of compass values for {@link com.mapbox.mapboxsdk.location.modes.CameraMode} + * and {@link com.mapbox.mapboxsdk.location.modes.RenderMode}. + * + * @return whether smooth compass animation is enabled + */ + public boolean compassAnimationEnabled() { + return compassAnimationEnabled; + } + + /** + * Enable or disable smooth animation of the accuracy circle around the user's position. + * + * @return whether smooth animation of the accuracy circle is enabled + */ + public boolean accuracyAnimationEnabled() { + return accuracyAnimationEnabled; + } + + @NonNull @Override public String toString() { return "LocationComponentOptions{" @@ -882,6 +916,10 @@ public class LocationComponentOptions implements Parcelable { h$ ^= Float.floatToIntBits(trackingMultiFingerMoveThreshold); h$ *= 1000003; h$ ^= Float.floatToIntBits(trackingAnimationDurationMultiplier); + h$ *= 1000003; + h$ ^= compassAnimationEnabled ? 1231 : 1237; + h$ *= 1000003; + h$ ^= accuracyAnimationEnabled ? 1231 : 1237; return h$; } @@ -921,7 +959,9 @@ public class LocationComponentOptions implements Parcelable { in.readFloat(), in.readFloat(), in.readString(), - in.readFloat() + in.readFloat(), + in.readInt() == 1, + in.readInt() == 1 ); } @@ -1020,6 +1060,8 @@ public class LocationComponentOptions implements Parcelable { dest.writeFloat(trackingMultiFingerMoveThreshold()); dest.writeString(layerBelow()); dest.writeFloat(trackingAnimationDurationMultiplier); + dest.writeInt(compassAnimationEnabled() ? 1 : 0); + dest.writeInt(accuracyAnimationEnabled() ? 1 : 0); } @Override @@ -1084,6 +1126,8 @@ public class LocationComponentOptions implements Parcelable { private Float trackingMultiFingerMoveThreshold; private String layerBelow; private Float trackingAnimationDurationMultiplier; + private Boolean compassAnimationEnabled; + private Boolean accuracyAnimationEnabled; Builder() { } @@ -1121,6 +1165,8 @@ public class LocationComponentOptions implements Parcelable { this.trackingMultiFingerMoveThreshold = source.trackingMultiFingerMoveThreshold(); this.layerBelow = source.layerBelow(); this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier(); + this.compassAnimationEnabled = source.compassAnimationEnabled(); + this.accuracyAnimationEnabled = source.accuracyAnimationEnabled(); } /** @@ -1556,6 +1602,28 @@ public class LocationComponentOptions implements Parcelable { return this; } + /** + * Enable or disable smooth animation of compass values for {@link com.mapbox.mapboxsdk.location.modes.CameraMode} + * and {@link com.mapbox.mapboxsdk.location.modes.RenderMode}. + * + * @return whether smooth compass animation is enabled + */ + public LocationComponentOptions.Builder compassAnimationEnabled(Boolean compassAnimationEnabled) { + this.compassAnimationEnabled = compassAnimationEnabled; + return this; + } + + /** + * Enable or disable smooth animation of the accuracy circle around the user's position. + * + * @return whether smooth animation of the accuracy circle is enabled + */ + public Builder accuracyAnimationEnabled(Boolean accuracyAnimationEnabled) { + this.accuracyAnimationEnabled = accuracyAnimationEnabled; + return this; + } + + @Nullable LocationComponentOptions autoBuild() { String missing = ""; if (this.accuracyAlpha == null) { @@ -1653,7 +1721,9 @@ public class LocationComponentOptions implements Parcelable { this.trackingInitialMoveThreshold, this.trackingMultiFingerMoveThreshold, this.layerBelow, - this.trackingAnimationDurationMultiplier); + this.trackingAnimationDurationMultiplier, + this.compassAnimationEnabled, + this.accuracyAnimationEnabled); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java index da3144eea1..fc4dd1d872 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationLayerController.java @@ -11,8 +11,8 @@ import com.google.gson.JsonObject; import com.mapbox.geojson.Feature; import com.mapbox.geojson.Point; import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.location.modes.RenderMode; +import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; @@ -192,6 +192,10 @@ final class LocationLayerController implements MapboxAnimator.OnLayerAnimationsV return isHidden; } + boolean isConsumingCompass() { + return renderMode == RenderMode.COMPASS; + } + private void setLayerVisibility(String layerId, boolean visible) { Layer layer = mapboxMap.getLayer(layerId); if (layer != null) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorSetProvider.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorSetProvider.java new file mode 100644 index 0000000000..1d09f8ae71 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/MapboxAnimatorSetProvider.java @@ -0,0 +1,32 @@ +package com.mapbox.mapboxsdk.location; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.support.annotation.NonNull; +import android.view.animation.Interpolator; + +import java.util.List; + +class MapboxAnimatorSetProvider { + private static MapboxAnimatorSetProvider instance; + + private MapboxAnimatorSetProvider() { + // private constructor + } + + static MapboxAnimatorSetProvider getInstance() { + if (instance == null) { + instance = new MapboxAnimatorSetProvider(); + } + return instance; + } + + void startAnimation(@NonNull List<Animator> animators, @NonNull Interpolator interpolator, + long duration) { + AnimatorSet locationAnimatorSet = new AnimatorSet(); + locationAnimatorSet.playTogether(animators); + locationAnimatorSet.setInterpolator(interpolator); + locationAnimatorSet.setDuration(duration); + locationAnimatorSet.start(); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java index 553678709e..45911a99b3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/Utils.java @@ -14,7 +14,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.Projection; + +import static com.mapbox.mapboxsdk.location.LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD; public final class Utils { @@ -88,6 +92,12 @@ public final class Utils { return (float) (location.getAccuracy() * (1 / metersPerPixel)); } + static boolean immediateAnimation(@NonNull Projection projection, @NonNull LatLng current, @NonNull LatLng target) { + double metersPerPixel = projection.getMetersPerPixelAtLatitude((current.getLatitude() + target.getLatitude()) / 2); + double distance = current.distanceTo(target); + return distance / metersPerPixel > INSTANT_LOCATION_TRANSITION_THRESHOLD; + } + /** * Casts the value to an even integer. */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java index 3eaa381239..69ae499a31 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapChangeReceiver.java @@ -27,6 +27,8 @@ class MapChangeReceiver implements NativeMapView.StateCallback { = new CopyOnWriteArrayList<>(); private final List<MapView.OnDidFinishRenderingMapListener> onDidFinishRenderingMapListenerList = new CopyOnWriteArrayList<>(); + private final List<MapView.OnDidBecomeIdleListener> onDidBecomeIdleListenerList + = new CopyOnWriteArrayList<>(); private final List<MapView.OnDidFinishLoadingStyleListener> onDidFinishLoadingStyleListenerList = new CopyOnWriteArrayList<>(); private final List<MapView.OnSourceChangedListener> onSourceChangedListenerList = new CopyOnWriteArrayList<>(); @@ -172,6 +174,20 @@ class MapChangeReceiver implements NativeMapView.StateCallback { } @Override + public void onDidBecomeIdle() { + try { + if (!onDidBecomeIdleListenerList.isEmpty()) { + for (MapView.OnDidBecomeIdleListener listener : onDidBecomeIdleListenerList) { + listener.onDidBecomeIdle(); + } + } + } catch (Throwable err) { + Logger.e(TAG, "Exception in onDidBecomeIdle", err); + throw err; + } + } + + @Override public void onDidFinishLoadingStyle() { try { if (!onDidFinishLoadingStyleListenerList.isEmpty()) { @@ -279,6 +295,14 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onDidFinishRenderingMapListenerList.remove(listener); } + void addOnDidBecomeIdleListener(MapView.OnDidBecomeIdleListener listener) { + onDidBecomeIdleListenerList.add(listener); + } + + void removeOnDidBecomeIdleListener(MapView.OnDidBecomeIdleListener listener) { + onDidBecomeIdleListenerList.remove(listener); + } + void addOnDidFinishLoadingStyleListener(MapView.OnDidFinishLoadingStyleListener listener) { onDidFinishLoadingStyleListenerList.add(listener); } @@ -306,6 +330,7 @@ class MapChangeReceiver implements NativeMapView.StateCallback { onDidFinishRenderingFrameList.clear(); onWillStartRenderingMapListenerList.clear(); onDidFinishRenderingMapListenerList.clear(); + onDidBecomeIdleListenerList.clear(); onDidFinishLoadingStyleListenerList.clear(); onSourceChangedListenerList.clear(); } 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 1997375f9e..0fe1e27662 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 @@ -96,6 +96,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { private MapKeyListener mapKeyListener; private MapZoomButtonController mapZoomButtonController; private Bundle savedInstanceState; + private boolean isActivated; @UiThread public MapView(@NonNull Context context) { @@ -371,8 +372,11 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { */ @UiThread public void onStart() { - ConnectivityReceiver.instance(getContext()).activate(); - FileSource.getInstance(getContext()).activate(); + if (!isActivated) { + ConnectivityReceiver.instance(getContext()).activate(); + FileSource.getInstance(getContext()).activate(); + isActivated = true; + } if (mapboxMap != null) { mapboxMap.onStart(); } @@ -417,8 +421,11 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { mapRenderer.onStop(); } - ConnectivityReceiver.instance(getContext()).deactivate(); - FileSource.getInstance(getContext()).deactivate(); + if (isActivated) { + ConnectivityReceiver.instance(getContext()).deactivate(); + FileSource.getInstance(getContext()).deactivate(); + isActivated = false; + } } /** @@ -851,13 +858,33 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { /** * Remove a callback that's invoked when the map has finished rendering. * - * @param listener The callback that's invoked when the map has finished rendering + * @param listener The callback that's invoked when the map has has finished rendering. */ public void removeOnDidFinishRenderingMapListener(OnDidFinishRenderingMapListener listener) { mapChangeReceiver.removeOnDidFinishRenderingMapListener(listener); } /** + * Set a callback that's invoked when the map has entered the idle state. + * + * @param listener The callback that's invoked when the map has entered the idle state. + */ + public void addOnDidBecomeIdleListener(OnDidBecomeIdleListener listener) { + mapChangeReceiver.addOnDidBecomeIdleListener(listener); + } + + /** + * Remove a callback that's invoked when the map has entered the idle state. + * + * @param listener The callback that's invoked when the map has entered the idle state. + */ + public void removeOnDidBecomeIdleListener(OnDidBecomeIdleListener listener) { + mapChangeReceiver.removeOnDidBecomeIdleListener(listener); + } + + /** + + /** * Set a callback that's invoked when the style has finished loading. * * @param listener The callback that's invoked when the style has finished loading @@ -1031,6 +1058,19 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { } /** + * Interface definition for a callback to be invoked when the map has entered the idle state. + * <p> + * {@link MapView#addOnDidBecomeIdleListener(OnDidBecomeIdleListener)} + * </p> + */ + public interface OnDidBecomeIdleListener { + /** + * Called when the map has entered the idle state. + */ + void onDidBecomeIdle(); + } + + /** * Interface definition for a callback to be invoked when the map has loaded the style. * <p> * {@link MapView#addOnDidFailLoadingMapListener(OnDidFailLoadingMapListener)} 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 48c571ee98..7734e619f1 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 @@ -993,6 +993,11 @@ final class NativeMapView { } @Keep + private void onDidBecomeIdle() { + stateCallback.onDidBecomeIdle(); + } + + @Keep private void onDidFinishLoadingStyle() { stateCallback.onDidFinishLoadingStyle(); // deprecated API @@ -1445,6 +1450,8 @@ final class NativeMapView { void onDidFinishLoadingStyle(); + void onDidBecomeIdle(); + void onSourceChanged(String sourceId); } } 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 970a7fc3b4..f284a636e6 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 @@ -13,6 +13,7 @@ import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v4.content.res.ResourcesCompat; import android.text.Html; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; @@ -97,6 +98,7 @@ public class MapSnapshotter { private CameraPosition cameraPosition; private boolean showLogo = true; private String localIdeographFontFamily; + private String apiBaseUrl; /** * @param width the width of the image @@ -184,6 +186,18 @@ public class MapSnapshotter { } /** + * Specifies the URL used for API endpoint. + * + * @param apiBaseUrl The base of our API endpoint + * @return the mutated {@link Options} + */ + @NonNull + public Options withApiBaseUrl(String apiBaseUrl) { + this.apiBaseUrl = apiBaseUrl; + return this; + } + + /** * @return the width of the image */ public int getWidth() { @@ -234,6 +248,13 @@ public class MapSnapshotter { return localIdeographFontFamily; } + /** + * @return The base of our API endpoint + */ + @Nullable + public String getApiBaseUrl() { + return apiBaseUrl; + } } /** @@ -251,6 +272,11 @@ public class MapSnapshotter { telemetry.onAppUserTurnstileEvent(); } FileSource fileSource = FileSource.getInstance(context); + String apiBaseUrl = options.getApiBaseUrl(); + if (!TextUtils.isEmpty(apiBaseUrl)) { + fileSource.setApiBaseUrl(apiBaseUrl); + } + String programCacheDir = FileSource.getInternalCachePath(context); nativeInitialize(this, fileSource, options.pixelRatio, options.width, 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 f180eda809..122349328f 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 @@ -14,6 +14,7 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.exceptions.ConversionException; import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -147,7 +148,10 @@ public class ColorUtils { * @return String rgba color */ public static String colorToRgbaString(@ColorInt int color) { - String alpha = new DecimalFormat("#.###").format(((float) ((color >> 24) & 0xFF)) / 255.0f); + NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US); + DecimalFormat decimalFormat = (DecimalFormat) numberFormat; + decimalFormat.applyPattern("#.###"); + String alpha = decimalFormat.format(((float) ((color >> 24) & 0xFF)) / 255.0f); return String.format(Locale.US, "rgba(%d, %d, %d, %s)", (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, alpha); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index 730ab580d0..4e9d1c5d8d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -152,4 +152,10 @@ <!-- Animation duration multiplier --> <public name="mapbox_trackingAnimationDurationMultiplier" format="float" type="attr" /> + + <!-- Compass animation --> + <public name="mapbox_compassAnimationEnabled" format="boolean" type="attr" /> + + <!-- Accuracy animation--> + <public name="mapbox_accuracyAnimationEnabled" format="boolean" type="attr" /> </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml index 9f0ca86a86..ed2dbe01df 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml @@ -178,5 +178,11 @@ <!-- Animation duration multiplier --> <attr name="mapbox_trackingAnimationDurationMultiplier" format="float" /> + <!-- Compass animation --> + <attr name="mapbox_compassAnimationEnabled" format="boolean" /> + + <!-- Accuracy animation--> + <attr name="mapbox_accuracyAnimationEnabled" format="boolean" /> + </declare-styleable> </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/CompassEngineTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/CompassEngineTest.java index bc64379263..bebd828118 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/CompassEngineTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/CompassEngineTest.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.location; import android.hardware.Sensor; +import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.view.WindowManager; @@ -10,7 +11,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import static com.mapbox.mapboxsdk.location.LocationComponentCompassEngine.SENSOR_DELAY_MICROS; import static junit.framework.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -27,8 +31,15 @@ public class CompassEngineTest { @Mock private SensorManager sensorManager; + @Mock + private Sensor compassSensor; + + @Mock + private CompassListener compassListener; + @Before public void setUp() throws Exception { + when(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)).thenReturn(compassSensor); compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager); } @@ -61,4 +72,18 @@ public class CompassEngineTest { verify(sensorManager, times(1)).getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); } + + @Test + public void listener_registerOnAdd() { + compassEngine.addCompassListener(compassListener); + verify(sensorManager) + .registerListener(any(SensorEventListener.class), eq(compassSensor), eq(SENSOR_DELAY_MICROS)); + } + + @Test + public void listener_unregisterOnRemove() { + compassEngine.addCompassListener(compassListener); + compassEngine.removeCompassListener(compassListener); + verify(sensorManager).unregisterListener(any(SensorEventListener.class), eq(compassSensor)); + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt index 2c94642610..7c75a8cb75 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationAnimatorCoordinatorTest.kt @@ -1,17 +1,24 @@ package com.mapbox.mapboxsdk.location +import android.animation.Animator import android.location.Location +import android.view.animation.LinearInterpolator import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_TILT_ANIM_DURATION import com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_ZOOM_ANIM_DURATION import com.mapbox.mapboxsdk.location.MapboxAnimator.* +import com.mapbox.mapboxsdk.maps.Projection import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) @@ -20,9 +27,12 @@ class LocationAnimatorCoordinatorTest { private lateinit var locationAnimatorCoordinator: LocationAnimatorCoordinator private val cameraPosition: CameraPosition = CameraPosition.DEFAULT + private lateinit var animatorSetProvider: MapboxAnimatorSetProvider + @Before fun setUp() { - locationAnimatorCoordinator = LocationAnimatorCoordinator() + animatorSetProvider = mock(MapboxAnimatorSetProvider::class.java) + locationAnimatorCoordinator = LocationAnimatorCoordinator(mock(Projection::class.java), animatorSetProvider) } @Test @@ -125,9 +135,6 @@ class LocationAnimatorCoordinatorTest { val layerAccuracy = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]?.target as Float assertEquals(layerAccuracy, accuracy) - - val animationDuration = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]?.duration as Long - assertEquals(LocationComponentConstants.ACCURACY_RADIUS_ANIMATION_DURATION, animationDuration) } @Test @@ -144,9 +151,6 @@ class LocationAnimatorCoordinatorTest { val layerAccuracy = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]?.target as Float assertEquals(layerAccuracy, accuracy) - - val animationDuration = locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]?.duration as Long - assertEquals(0L, animationDuration) } @Test @@ -281,4 +285,50 @@ class LocationAnimatorCoordinatorTest { assertTrue(locationAnimatorCoordinator.cameraListeners.isEmpty()) } + + @Test + fun feedNewCompassBearing_withAnimation() { + locationAnimatorCoordinator.setCompassAnimationEnabled(true) + locationAnimatorCoordinator.feedNewCompassBearing(77f, cameraPosition) + + val animators = mutableListOf<Animator>( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_COMPASS_BEARING], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_COMPASS_BEARING]) + + verify(animatorSetProvider).startAnimation(eq(animators), any(LinearInterpolator::class.java), eq(LocationComponentConstants.COMPASS_UPDATE_RATE_MS)) + } + + @Test + fun feedNewCompassBearing_withoutAnimation() { + locationAnimatorCoordinator.setCompassAnimationEnabled(false) + locationAnimatorCoordinator.feedNewCompassBearing(77f, cameraPosition) + + val animators = mutableListOf<Animator>( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_COMPASS_BEARING], + locationAnimatorCoordinator.animatorArray[ANIMATOR_CAMERA_COMPASS_BEARING]) + + verify(animatorSetProvider).startAnimation(eq(animators), any(LinearInterpolator::class.java), eq(0L)) + } + + @Test + fun feedNewAccuracy_withAnimation() { + locationAnimatorCoordinator.setAccuracyAnimationEnabled(true) + locationAnimatorCoordinator.feedNewAccuracyRadius(150f, false) + + val animators = mutableListOf<Animator>( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]) + + verify(animatorSetProvider).startAnimation(eq(animators), any(LinearInterpolator::class.java), eq(LocationComponentConstants.ACCURACY_RADIUS_ANIMATION_DURATION)) + } + + @Test + fun feedNewAccuracy_withoutAnimation() { + locationAnimatorCoordinator.setAccuracyAnimationEnabled(false) + locationAnimatorCoordinator.feedNewAccuracyRadius(150f, false) + + val animators = mutableListOf<Animator>( + locationAnimatorCoordinator.animatorArray[ANIMATOR_LAYER_ACCURACY]) + + verify(animatorSetProvider).startAnimation(eq(animators), any(LinearInterpolator::class.java), eq(0L)) + } }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java index 9e26bb1a60..da2773c592 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationCameraControllerTest.java @@ -416,6 +416,9 @@ public class LocationCameraControllerTest { private LocationCameraController buildCamera(OnCameraTrackingChangedListener onCameraTrackingChangedListener) { MapboxMap mapboxMap = mock(MapboxMap.class); when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class)); + Projection projection = mock(Projection.class); + when(mapboxMap.getProjection()).thenReturn(projection); + when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000)); MoveGestureDetector moveGestureDetector = mock(MoveGestureDetector.class); OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class); AndroidGesturesManager initialGesturesManager = mock(AndroidGesturesManager.class); @@ -427,6 +430,9 @@ public class LocationCameraControllerTest { private LocationCameraController buildCamera(MoveGestureDetector moveGestureDetector) { MapboxMap mapboxMap = mock(MapboxMap.class); when(mapboxMap.getUiSettings()).thenReturn(mock(UiSettings.class)); + Projection projection = mock(Projection.class); + when(mapboxMap.getProjection()).thenReturn(projection); + when(projection.getMetersPerPixelAtLatitude(any(Double.class))).thenReturn(Double.valueOf(1000)); OnCameraTrackingChangedListener onCameraTrackingChangedListener = mock(OnCameraTrackingChangedListener.class); OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = mock(OnCameraMoveInvalidateListener.class); AndroidGesturesManager initialGesturesManager = mock(AndroidGesturesManager.class); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt new file mode 100644 index 0000000000..5879b8184f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt @@ -0,0 +1,214 @@ +package com.mapbox.mapboxsdk.location + +import android.content.Context +import com.mapbox.android.core.location.LocationEngine +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.location.modes.CameraMode +import com.mapbox.mapboxsdk.location.modes.RenderMode +import com.mapbox.mapboxsdk.maps.MapboxMap +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class LocationComponentTest { + private lateinit var locationComponent: LocationComponent + + @Mock + private lateinit var locationComponentOptions: LocationComponentOptions + + @Mock + private lateinit var mapboxMap: MapboxMap + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var locationEngine: LocationEngine + + @Mock + private lateinit var compassEngine: CompassEngine + + @Mock + private lateinit var locationLayerController: LocationLayerController + + @Mock + private lateinit var locationCameraController: LocationCameraController + + @Mock + private lateinit var locationAnimatorCoordinator: LocationAnimatorCoordinator + + @Mock + private lateinit var staleStateManager: StaleStateManager + + @Before + fun before() { + MockitoAnnotations.initMocks(this) + locationComponent = LocationComponent(mapboxMap, locationEngine, locationLayerController, + locationCameraController, locationAnimatorCoordinator, staleStateManager, compassEngine) + } + + @Test + fun compass_listenWhenConsumedByNoneCamera() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationCameraController.isConsumingCompass).thenReturn(true) + locationComponent.cameraMode = CameraMode.NONE_COMPASS + verify(compassEngine).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_listenWhenConsumedByTrackingCamera() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationCameraController.isConsumingCompass).thenReturn(true) + locationComponent.cameraMode = CameraMode.TRACKING_COMPASS + verify(compassEngine).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_listenWhenConsumedByLayer() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + verify(compassEngine).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_notListenWhenNotConsumed() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(false) + `when`(locationCameraController.isConsumingCompass).thenReturn(false) + locationComponent.renderMode = RenderMode.GPS + locationComponent.renderMode = RenderMode.NORMAL + locationComponent.cameraMode = CameraMode.TRACKING + locationComponent.cameraMode = CameraMode.NONE + locationComponent.cameraMode = CameraMode.NONE_GPS + locationComponent.cameraMode = CameraMode.TRACKING_GPS + locationComponent.cameraMode = CameraMode.TRACKING_GPS_NORTH + verify(compassEngine, never()).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_removeListenerOnChange() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + `when`(locationLayerController.isConsumingCompass).thenReturn(false) + locationComponent.renderMode = RenderMode.NORMAL + verify(compassEngine).removeCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_removeListenerOnStop() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + locationComponent.onStop() + verify(compassEngine).removeCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_reAddListenerOnStart() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + locationComponent.onStop() + locationComponent.onStart() + verify(compassEngine, times(2)).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_removeListenerOnStyleStartLoad() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + locationComponent.onStartLoadingMap() + verify(compassEngine).removeCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_reAddListenerOnStyleLoadFinished() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + locationComponent.onStartLoadingMap() + locationComponent.onFinishLoadingStyle() + verify(compassEngine, times(2)).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_reAddListenerOnlyWhenEnabled() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + locationComponent.isLocationComponentEnabled = false + locationComponent.onStartLoadingMap() + locationComponent.onFinishLoadingStyle() + verify(compassEngine).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_notAdListenerWhenDisabled() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.onStart() + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + verify(compassEngine, never()).addCompassListener(any(CompassListener::class.java)) + } + + @Test + fun compass_notAdListenerWhenStopped() { + locationComponent.activateLocationComponent(context, locationEngine, locationComponentOptions) + locationComponent.isLocationComponentEnabled = true + `when`(mapboxMap.cameraPosition).thenReturn(CameraPosition.DEFAULT) + + `when`(locationLayerController.isConsumingCompass).thenReturn(true) + locationComponent.renderMode = RenderMode.COMPASS + verify(compassEngine, never()).addCompassListener(any(CompassListener::class.java)) + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java index 688b4badec..913c47fcd6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapChangeReceiverTest.java @@ -1,8 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import com.google.common.util.concurrent.ExecutionError; import com.mapbox.mapboxsdk.log.Logger; import com.mapbox.mapboxsdk.log.LoggerDefinition; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -53,6 +55,9 @@ public class MapChangeReceiverTest { private MapView.OnDidFinishRenderingMapListener onDidFinishRenderingMapListener; @Mock + private MapView.OnDidBecomeIdleListener onDidBecomeIdleListener; + + @Mock private MapView.OnDidFinishLoadingStyleListener onDidFinishLoadingStyleListener; @Mock @@ -307,6 +312,36 @@ public class MapChangeReceiverTest { } @Test + public void testOnDidBecomeIdleListener() { + mapChangeEventManager.addOnDidBecomeIdleListener(onDidBecomeIdleListener); + mapChangeEventManager.onDidBecomeIdle(); + verify(onDidBecomeIdleListener).onDidBecomeIdle(); + mapChangeEventManager.removeOnDidBecomeIdleListener(onDidBecomeIdleListener); + mapChangeEventManager.onDidBecomeIdle(); + verify(onDidBecomeIdleListener).onDidBecomeIdle(); + + mapChangeEventManager.addOnDidBecomeIdleListener(onDidBecomeIdleListener); + Logger.setLoggerDefinition(loggerDefinition); + Exception exc = new RuntimeException(); + doThrow(exc).when(onDidBecomeIdleListener).onDidBecomeIdle(); + try { + mapChangeEventManager.onDidBecomeIdle(); + Assert.fail("The exception should've been re-thrown."); + } catch (RuntimeException throwable) { + verify(loggerDefinition).e(anyString(), anyString(), eq(exc)); + } + + Error err = new ExecutionError("", new Error()); + doThrow(err).when(onDidBecomeIdleListener).onDidBecomeIdle(); + try { + mapChangeEventManager.onDidBecomeIdle(); + Assert.fail("The exception should've been re-thrown."); + } catch (ExecutionError throwable) { + verify(loggerDefinition).e(anyString(), anyString(), eq(err)); + } + } + + @Test public void testOnDidFinishLoadingStyleListener() { mapChangeEventManager.addOnDidFinishLoadingStyleListener(onDidFinishLoadingStyleListener); mapChangeEventManager.onDidFinishLoadingStyle(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt index 444b478219..41e67ca31b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/location/LocationComponentTest.kt @@ -1106,12 +1106,15 @@ class LocationComponentTest : BaseActivityTest() { uiController: UiController, context: Context) { component.activateLocationComponent(context, false) component.isLocationComponentEnabled = true - val target = LatLng(51.0, 17.0) - assertTrue(target.distanceTo(LatLng(location)) > LocationComponentConstants.INSTANT_LOCATION_TRANSITION_THRESHOLD) component.cameraMode = CameraMode.NONE component.forceLocationUpdate(location) + + val target = LatLng(51.0, 17.0) mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(target)) + mapboxMap.moveCamera(CameraUpdateFactory.zoomTo(15.0)) mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(90.0)) + assertTrue(Utils.immediateAnimation(mapboxMap.projection, mapboxMap.cameraPosition.target, LatLng(location))) + component.cameraMode = CameraMode.TRACKING_GPS assertEquals(location.bearing.toDouble(), mapboxMap.cameraPosition.bearing, 0.1) assertEquals(location.latitude, mapboxMap.cameraPosition.target.latitude, 0.1) diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxTest.java index 3648aaeebe..7c84a92ae0 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/MapboxTest.java @@ -6,10 +6,14 @@ import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; import org.junit.Test; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; public class MapboxTest extends BaseActivityTest { + private static final String ACCESS_TOKEN = "pk.0000000001"; + private static final String ACCESS_TOKEN_2 = "pk.0000000002"; + @Override protected Class getActivityClass() { return EspressoTestActivity.class; @@ -30,4 +34,15 @@ public class MapboxTest extends BaseActivityTest { Mapbox.setConnected(null); assertTrue(Mapbox.isConnected()); } + + @Test + public void setAccessToken() { + validateTestSetup(); + String realToken = Mapbox.getAccessToken(); + Mapbox.setAccessToken(ACCESS_TOKEN); + assertSame(ACCESS_TOKEN, Mapbox.getAccessToken()); + Mapbox.setAccessToken(ACCESS_TOKEN_2); + assertSame(ACCESS_TOKEN_2, Mapbox.getAccessToken()); + Mapbox.setAccessToken(realToken); + } }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 14066123d3..1ec0116047 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -873,6 +873,17 @@ android:value=".activity.FeatureOverviewActivity" /> </activity> + <activity + android:name=".activity.maplayout.RecyclerViewActivity" + android:description="@string/description_recyclerview" + android:label="@string/activity_recyclerview"> + <meta-data + android:name="@string/category" + android:value="@string/category_maplayout" /> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".activity.FeatureOverviewActivity" /> + </activity> <!-- For Instrumentation tests --> <activity android:name=".activity.style.RuntimeStyleTestActivity" diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java index e3af101a8d..c7d45a879a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/MapChangeActivity.java @@ -33,6 +33,7 @@ public class MapChangeActivity extends AppCompatActivity { mapView.addOnDidFinishLoadingStyleListener(() -> Timber.v("OnDidFinishLoadingStyle")); mapView.addOnDidFinishRenderingFrameListener(fully -> Timber.v("OnDidFinishRenderingFrame: fully: %s", fully)); mapView.addOnDidFinishRenderingMapListener(fully -> Timber.v("OnDidFinishRenderingMap: fully: %s", fully)); + mapView.addOnDidBecomeIdleListener(() -> Timber.v("OnDidBecomeIdle")); mapView.addOnSourceChangedListener(sourceId -> Timber.v("OnSourceChangedListener: source with id: %s", sourceId)); mapView.addOnWillStartLoadingMapListener(() -> Timber.v("OnWillStartLoadingMap")); mapView.addOnWillStartRenderingFrameListener(() -> Timber.v("OnWillStartRenderingFrame")); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/RecyclerViewActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/RecyclerViewActivity.kt new file mode 100644 index 0000000000..d35c977df0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/maplayout/RecyclerViewActivity.kt @@ -0,0 +1,148 @@ +package com.mapbox.mapboxsdk.testapp.activity.maplayout + +import android.annotation.SuppressLint +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.testapp.R +import kotlinx.android.synthetic.main.activity_recyclerview.* + +/** + * TestActivity showcasing how to integrate a MapView in a RecyclerView. + * <p> + * It requires calling the correct lifecycle methods when detaching and attaching the View to + * the RecyclerView with onViewAttachedToWindow and onViewDetachedFromWindow. + * </p> + */ +@SuppressLint("ClickableViewAccessibility") +class RecyclerViewActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_recyclerview) + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = ItemAdapter(LayoutInflater.from(this), savedInstanceState) + } + + override fun onSaveInstanceState(outState: Bundle?) { + super.onSaveInstanceState(outState) + // to save state, we need to call MapView#onSaveInstanceState + (recyclerView.adapter as ItemAdapter).onSaveInstanceState(outState) + } + + override fun onLowMemory() { + super.onLowMemory() + // to release memory, we need to call MapView#onLowMemory + (recyclerView.adapter as ItemAdapter).onLowMemory() + } + + override fun onDestroy() { + super.onDestroy() + // to perform cleanup, we need to call MapView#onDestroy + (recyclerView.adapter as ItemAdapter).onDestroy() + } + + class ItemAdapter(private val inflater: LayoutInflater, val savedInstanceState: Bundle?) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { + + private val items = listOf( + "one", "two", "three", MapItem(), "four", "five", "six", "seven", "eight", "nine", "ten", + "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", + "nineteen", "twenty", "twenty-one" + ) + + private var mapHolder: MapHolder? = null + + companion object { + const val TYPE_MAP = 0 + const val TYPE_TEXT = 1 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == TYPE_MAP) { + val mapView = inflater.inflate(R.layout.item_map, parent, false) as MapView + mapHolder = MapHolder(mapView, savedInstanceState) + return mapHolder as MapHolder + } else { + TextHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false) as TextView) + } + } + + override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { + super.onViewAttachedToWindow(holder) + if (holder is MapHolder) { + val mapView = holder.mapView + mapView.onStart() + mapView.onResume() + } + } + + override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { + super.onViewDetachedFromWindow(holder) + if (holder is MapHolder) { + val mapView = holder.mapView + mapView.onPause() + mapView.onStop() + } + } + + override fun getItemCount(): Int { + return items.count() + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder.itemViewType == TYPE_TEXT) { + val textHolder = holder as TextHolder + textHolder.bind(items[position] as String) + } + } + + override fun getItemViewType(position: Int): Int { + return if (items[position] is MapItem) { + TYPE_MAP + } else { + TYPE_TEXT + } + } + + fun onSaveInstanceState(savedInstanceState: Bundle?){ + savedInstanceState?.let { + mapHolder?.mapView?.onSaveInstanceState(it) + } + } + + fun onLowMemory() { + mapHolder?.mapView?.onLowMemory() + } + + fun onDestroy() { + mapHolder?.mapView?.let { + it.onPause() + it.onStop() + it.onDestroy() + } + } + + class MapItem + class MapHolder(val mapView: MapView, bundle: Bundle?) : RecyclerView.ViewHolder(mapView) { + init { + mapView.onCreate(bundle) + mapView.setOnTouchListener { view, motionEvent -> + // Disallow the touch request for recyclerView scroll + view.parent.requestDisallowInterceptTouchEvent(true) + mapView.onTouchEvent(motionEvent) + true + } + } + } + class TextHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) { + fun bind(item: String) { + textView.text = item + } + } + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java index 9f1e7b9956..f98cffecc9 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java @@ -27,7 +27,6 @@ public class MapSnapshotterActivity extends AppCompatActivity { private GridLayout grid; private List<MapSnapshotter> snapshotters = new ArrayList<>(); - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -69,7 +68,7 @@ public class MapSnapshotterActivity extends AppCompatActivity { // Optionally the style .withStyle((column + row) % 2 == 0 ? Style.MAPBOX_STREETS : Style.DARK) - .withLocalIdeographFontFamily("sans-serif"); + .withLocalIdeographFontFamily("sans-serif"); // Optionally the visible region if (row % 2 == 0) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_multi_map.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_multi_map.xml index 51442894e1..3da56d1c82 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_multi_map.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_multi_map.xml @@ -24,7 +24,7 @@ mapbox:mapbox_cameraTargetLat="38.913187" mapbox:mapbox_cameraTargetLng="-77.032546" mapbox:mapbox_cameraZoom="12" - mapbox:mapbox_styleUrl="mapbox://styles/mapbox/streets-v10" /> + mapbox:mapbox_styleUrl="mapbox://styles/mapbox/streets-v11" /> <!-- SF --> <fragment @@ -36,7 +36,7 @@ mapbox:mapbox_cameraTargetLat="37.775732" mapbox:mapbox_cameraTargetLng="-122.413985" mapbox:mapbox_cameraZoom="13" - mapbox:mapbox_styleUrl="mapbox://styles/mapbox/outdoors-v10" /> + mapbox:mapbox_styleUrl="mapbox://styles/mapbox/outdoors-v11" /> </LinearLayout> <LinearLayout @@ -56,7 +56,7 @@ mapbox:mapbox_cameraTargetLat="12.97913" mapbox:mapbox_cameraTargetLng="77.59188" mapbox:mapbox_cameraZoom="14" - mapbox:mapbox_styleUrl="mapbox://styles/mapbox/light-v9" /> + mapbox:mapbox_styleUrl="mapbox://styles/mapbox/light-v10" /> <!-- Ayacucho --> <fragment @@ -68,6 +68,6 @@ mapbox:mapbox_cameraTargetLat="-13.155980" mapbox:mapbox_cameraTargetLng="-74.217134" mapbox:mapbox_cameraZoom="15" - mapbox:mapbox_styleUrl="mapbox://styles/mapbox/dark-v9" /> + mapbox:mapbox_styleUrl="mapbox://styles/mapbox/dark-v10" /> </LinearLayout> </LinearLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_recyclerview.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_recyclerview.xml new file mode 100644 index 0000000000..7d57f112b7 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_recyclerview.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</android.support.constraint.ConstraintLayout>
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_dialog_map.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_dialog_map.xml index b8ea3d847e..9e5312f067 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_dialog_map.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_dialog_map.xml @@ -12,6 +12,6 @@ mapbox:mapbox_cameraTargetLat="47.6077" mapbox:mapbox_cameraTargetLng="-122.3421" mapbox:mapbox_cameraZoom="11" - mapbox:mapbox_styleUrl="mapbox://styles/mapbox/streets-v10" /> + mapbox:mapbox_styleUrl="mapbox://styles/mapbox/streets-v11" /> </LinearLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/item_map.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/item_map.xml new file mode 100644 index 0000000000..c4c17a25ff --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/item_map.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.mapbox.mapboxsdk.maps.MapView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@id/mapView" + android:layout_width="match_parent" + android:layout_height="256dp" + app:mapbox_cameraTargetLat="45.38301927899065" + app:mapbox_cameraTargetLng="8.63525390625" + app:mapbox_renderTextureMode="true" + app:mapbox_cameraZoom="7" + app:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 67447bce74..abd46d4c8c 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -76,4 +76,5 @@ <string name="description_location_modes">Showcases location render and tracking modes</string> <string name="description_location_fragment">Uses LocationComponent in a Fragment</string> <string name="description_location_manual">Force location updates and don\'t rely on the engine</string> + <string name="description_recyclerview">Show a MapView as a recyclerView item</string> </resources> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml index a0525171a5..faa994e978 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml @@ -48,6 +48,8 @@ <item name="mapbox_accuracyColor">#FF82C6</item> <item name="mapbox_elevation">0dp</item> + <item name="mapbox_compassAnimationEnabled">false</item> + <item name="mapbox_accuracyAnimationEnabled">false</item> </style> </resources> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index efd7476c4d..dd7900478b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -76,4 +76,5 @@ <string name="activity_location_modes">Location Modes Activity</string> <string name="activity_location_fragment">Location Fragment</string> <string name="activity_location_manual">Manual Location updates</string> + <string name="activity_recyclerview">RecyclerView</string> </resources>
\ No newline at end of file diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle index 9f9f1f7547..bdff055b23 100644 --- a/platform/android/gradle/dependencies.gradle +++ b/platform/android/gradle/dependencies.gradle @@ -9,7 +9,7 @@ ext { versions = [ mapboxServices : '3.4.1', - mapboxTelemetry : '3.5.6', + mapboxTelemetry : '3.5.7', mapboxGestures : '0.3.0', supportLib : '27.1.1', constraintLayout: '1.1.2', diff --git a/platform/android/gradle/wrapper/gradle-wrapper.properties b/platform/android/gradle/wrapper/gradle-wrapper.properties index 84af82d181..1ebfd73849 100644 --- a/platform/android/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Mon May 14 12:12:39 CEST 2018 +#Wed Dec 19 09:48:04 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index f12c48f938..2145a23354 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -186,6 +186,15 @@ void NativeMapView::onDidFinishRenderingMap(MapObserver::RenderMode mode) { javaPeer.get(*_env).Call(*_env, onDidFinishRenderingMap, (jboolean) (mode != MapObserver::RenderMode::Partial)); } +void NativeMapView::onDidBecomeIdle() { + assert(vm != nullptr); + + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class<NativeMapView>::Singleton(*_env); + static auto onDidBecomeIdle = javaClass.GetMethod<void ()>(*_env, "onDidBecomeIdle"); + javaPeer.get(*_env).Call(*_env, onDidBecomeIdle); +} + void NativeMapView::onDidFinishLoadingStyle() { assert(vm != nullptr); diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 08f1efa46d..91d915d58f 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -67,6 +67,7 @@ public: void onDidFinishRenderingFrame(MapObserver::RenderMode) override; void onWillStartRenderingMap() override; void onDidFinishRenderingMap(MapObserver::RenderMode) override; + void onDidBecomeIdle() override; void onDidFinishLoadingStyle() override; void onSourceChanged(mbgl::style::Source&) override; diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 77dd2e4ef4..535f44a7fb 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -230,6 +230,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; /** + Tells the delegate that the map view is entering an idle state, and no more + drawing will be necessary until new data is loaded or there is some interaction + with the map. + + - No camera transitions are in progress + - All currently requested tiles have loaded + - All fade/transition animations have completed + + @param mapView The map view that has just entered the idle state. + */ +- (void)mapViewDidBecomeIdle:(MGLMapView *)mapView; + +/** Tells the delegate that the map has just finished loading a style. This method is called during the initialization of the map view and after any diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 4904cb185b..2bf3dc06bd 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -52,6 +52,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {} func mapViewDidFinishRenderingMap(_ mapView: MGLMapView, fullyRendered: Bool) {} + + func mapViewDidBecomeIdle(_ mapView: MGLMapView) {} func mapView(_ mapView: MGLMapView, didFailToLocateUserWithError error: Error) {} diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 9f9bb855d2..d142271576 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -928,6 +928,16 @@ public: } } +- (void)mapViewDidBecomeIdle { + if (!_mbglMap) { + return; + } + + if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle)]) { + [self.delegate mapViewDidBecomeIdle:self]; + } +} + - (void)mapViewDidFinishLoadingStyle { if (!_mbglMap) { return; @@ -3012,6 +3022,10 @@ public: bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; [nativeView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; } + + void onDidBecomeIdle() override { + [nativeView mapViewDidBecomeIdle]; + } void onDidFinishLoadingStyle() override { [nativeView mapViewDidFinishLoadingStyle]; diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h index 2a8b28c1b4..c7d6786666 100644 --- a/platform/macos/src/MGLMapViewDelegate.h +++ b/platform/macos/src/MGLMapViewDelegate.h @@ -152,6 +152,19 @@ NS_ASSUME_NONNULL_BEGIN - (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; /** + Tells the delegate that the map view is entering an idle state, and no more + drawing will be necessary until new data is loaded or there is some interaction + with the map. + + - No camera transitions are in progress + - All currently requested tiles have loaded + - All fade/transition animations have completed + + @param mapView The map view that has just entered the idle state. + */ +- (void)mapViewDidBecomeIdle:(MGLMapView *)mapView; + +/** Tells the delegate that the map has just finished loading a style. This method is called during the initialization of the map view and after any diff --git a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift index 00635d97eb..83c7160fde 100644 --- a/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/macos/test/MGLMapViewDelegateIntegrationTests.swift @@ -24,6 +24,8 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapViewDidFinishRenderingFrame(_ mapView: MGLMapView, fullyRendered: Bool) {} func mapViewDidFinishRenderingMap(_ mapView: MGLMapView, fullyRendered: Bool) {} + + func mapViewDidBecomeIdle(_ mapView: MGLMapView) {} func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {} diff --git a/scripts/changelog_staging/idle-event.json b/scripts/changelog_staging/idle-event.json new file mode 100644 index 0000000000..96db06eb99 --- /dev/null +++ b/scripts/changelog_staging/idle-event.json @@ -0,0 +1,6 @@ +{ + "core": "Add onDidEnterIdle to MapObserver, which fires whenever render completes and no repaint is scheduled.", + "darwin": "Add mapViewDidEnterIdle to MGLMapViewDelegate, which fires whenever render completes and no repaint is scheduled.", + "android": "Add onDidEnterIdle listener to MapView, which fires whenever render completes and no repaint is scheduled.", + "issue": 13469 +} diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 1f64cf3acc..dc16fe46ce 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -208,6 +208,8 @@ void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode, bool needsRepai if (needsRepaint || transform.inTransition()) { onUpdate(); + } else if (rendererFullyLoaded) { + observer.onDidBecomeIdle(); } } else if (stillImageRequest && rendererFullyLoaded) { auto request = std::move(stillImageRequest); |