From f4ad769a5b60b7443e650e1633e3d9ae6066ab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Mon, 12 Nov 2018 17:11:51 +0100 Subject: [android] refactor LocationComponent to utilize LocationEngineRequest --- .../mapboxsdk/location/LocationComponent.java | 286 +++++++++++++++------ .../location/LocationComponentConstants.java | 6 + .../location/LocationComponentOptions.java | 2 +- 3 files changed, 219 insertions(+), 75 deletions(-) (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox') 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 d0b0bb733d..b2f1206d06 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 @@ -9,6 +9,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresPermission; import android.support.annotation.StyleRes; +import android.support.annotation.VisibleForTesting; import android.view.WindowManager; import com.mapbox.android.core.location.LocationEngine; @@ -28,13 +29,13 @@ import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; import com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener; +import java.lang.ref.WeakReference; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_FASTEST_INTERVAL_MILLIS; +import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_INTERVAL_MILLIS; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_TILT_ANIM_DURATION; import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_ZOOM_ANIM_DURATION; @@ -60,14 +61,23 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_T *

* Using this component requires you to request permission beforehand manually or using * {@link com.mapbox.android.core.permissions.PermissionsManager}. Either - * {@code ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permissions can be requested and - * this plugin work as expected. + * {@code ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permissions can be requested for + * this component to work as expected. *

* This component offers a default, built-in {@link LocationEngine} with some of the activation methods. * This engine will be obtained by {@link LocationEngineProvider#getBestLocationEngine(Context, boolean)} which defaults - * to the {@link com.mapbox.android.core.location.MapboxFusedLocationEngineImpl}. If you'd like to utilize Google Play Services + * to the {@link com.mapbox.android.core.location.MapboxFusedLocationEngineImpl}. If you'd like to utilize Google + * Play Services * for more precise location updates, simply add the Google Play Location Services dependency in your build script. * This will make the default engine the {@link com.mapbox.android.core.location.GoogleLocationEngineImpl} instead. + * After a custom engine is passed to the component, or the built-in is initialized, + * the location updates are going to be requested with the {@link LocationEngineRequest}, either a default one, + * or the one passed during the activation. + * When using any engine, requesting/removing the location updates is going to be managed internally. + *

+ * You can also push location updates to the component without any internal engine management. + * To achieve that, use {@link #activateLocationComponent(Context, boolean)} with false. + * No engine is going to be initialized and you can push location updates with {@link #forceLocationUpdate(Location)}. *

* For location puck animation purposes, like navigation, * we recommend limiting the maximum zoom level of the map for the best user experience. @@ -76,17 +86,26 @@ import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_T */ public final class LocationComponent { private static final String TAG = "Mbgl-LocationComponent"; - private static final long DEFAULT_INTERVAL_MILLIS = 1000; - private static final long DEFAULT_FASTEST_INTERVAL_MILLIS = 1000; @NonNull private final MapboxMap mapboxMap; private LocationComponentOptions options; + @NonNull + private InternalLocationEngineProvider internalLocationEngineProvider = new InternalLocationEngineProvider(); @Nullable private LocationEngine locationEngine; - @Nullable + @NonNull + private LocationEngineRequest locationEngineRequest = + new LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS) + .setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS) + .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY) + .build(); + private LocationEngineCallback currentLocationEngineListener + = new CurrentLocationEngineCallback(this); + private LocationEngineCallback lastLocationEngineListener + = new LastLocationEngineCallback(this); + private CompassEngine compassEngine; - private boolean usingInternalLocationEngine; private LocationLayerController locationLayerController; private LocationCameraController locationCameraController; @@ -144,6 +163,28 @@ public final class LocationComponent { this.mapboxMap = mapboxMap; } + @VisibleForTesting + LocationComponent(@NonNull MapboxMap mapboxMap, + @NonNull LocationEngineCallback currentlistener, + @NonNull LocationEngineCallback lastListener, + @NonNull LocationLayerController locationLayerController, + @NonNull LocationCameraController locationCameraController, + @NonNull LocationAnimatorCoordinator locationAnimatorCoordinator, + @NonNull StaleStateManager staleStateManager, + @NonNull CompassEngine compassEngine, + @NonNull InternalLocationEngineProvider internalLocationEngineProvider) { + this.mapboxMap = mapboxMap; + this.currentLocationEngineListener = currentlistener; + this.lastLocationEngineListener = lastListener; + this.locationLayerController = locationLayerController; + this.locationCameraController = locationCameraController; + this.locationAnimatorCoordinator = locationAnimatorCoordinator; + this.staleStateManager = staleStateManager; + this.compassEngine = compassEngine; + this.internalLocationEngineProvider = internalLocationEngineProvider; + 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)}. @@ -154,8 +195,8 @@ public final class LocationComponent { */ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public void activateLocationComponent(@NonNull Context context) { - activateLocationComponent(context, LocationComponentOptions.createFromAttributes(context, R.style - .mapbox_LocationComponent)); + activateLocationComponent(context, + LocationComponentOptions.createFromAttributes(context, R.style.mapbox_LocationComponent)); } /** @@ -175,6 +216,26 @@ public final class LocationComponent { } } + /** + * 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)}. + * + * @param context the context + * @param useDefaultLocationEngine true if you want to initialize and use the built-in location engine or false if + * there should be no location engine initialized + * @param locationEngineRequest the location request + */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) + public void activateLocationComponent(@NonNull Context context, boolean useDefaultLocationEngine, + @NonNull LocationEngineRequest locationEngineRequest) { + setLocationEngineRequest(locationEngineRequest); + if (useDefaultLocationEngine) { + activateLocationComponent(context, R.style.mapbox_LocationComponent); + } else { + activateLocationComponent(context, null, R.style.mapbox_LocationComponent); + } + } + /** * 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)}. @@ -214,22 +275,53 @@ public final class LocationComponent { * @param locationEngine the engine, or null if you'd like to only force location updates * @param styleRes the LocationComponent style res */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine, @StyleRes int styleRes) { activateLocationComponent(context, locationEngine, LocationComponentOptions.createFromAttributes(context, styleRes)); } + /** + * 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)}. + * + * @param context the context + * @param locationEngine the engine, or null if you'd like to only force location updates + * @param locationEngineRequest the location request + * @param styleRes the LocationComponent style res + */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) + public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine, + @NonNull LocationEngineRequest locationEngineRequest, @StyleRes int styleRes) { + activateLocationComponent(context, locationEngine, locationEngineRequest, + LocationComponentOptions.createFromAttributes(context, styleRes)); + } + /** * This method will show the location icon and enable the camera tracking the location. * * @param context the context * @param locationEngine the engine */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public void activateLocationComponent(@NonNull Context context, @NonNull LocationEngine locationEngine) { activateLocationComponent(context, locationEngine, R.style.mapbox_LocationComponent); } + /** + * This method will show the location icon and enable the camera tracking the location. + * + * @param context the context + * @param locationEngine the engine + * @param locationEngineRequest the location request + */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) + public void activateLocationComponent(@NonNull Context context, @NonNull LocationEngine locationEngine, + @NonNull LocationEngineRequest locationEngineRequest) { + activateLocationComponent(context, locationEngine, locationEngineRequest, R.style.mapbox_LocationComponent); + } + /** * 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)}. @@ -237,6 +329,7 @@ public final class LocationComponent { * @param locationEngine the engine, or null if you'd like to only force location updates * @param options the options */ + @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine, @NonNull LocationComponentOptions options) { initialize(context, options); @@ -244,6 +337,24 @@ public final class LocationComponent { applyStyle(options); } + /** + * 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)}. + * + * @param context the context + * @param locationEngine the engine, or null if you'd like to only force location updates + * @param locationEngineRequest the location request + * @param options the options + */ + public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine, + @NonNull LocationEngineRequest locationEngineRequest, + @NonNull LocationComponentOptions options) { + initialize(context, options); + setLocationEngineRequest(locationEngineRequest); + setLocationEngine(locationEngine); + applyStyle(options); + } + /** * Manage component's visibility after activation. * @@ -491,22 +602,45 @@ public final class LocationComponent { * * @param locationEngine a {@link LocationEngine} this component should use to handle updates */ + @SuppressLint("MissingPermission") public void setLocationEngine(@Nullable LocationEngine locationEngine) { if (this.locationEngine != null) { // If internal location engines being used, extra steps need to be taken to deconstruct the // instance. - if (usingInternalLocationEngine) { - usingInternalLocationEngine = false; - } - this.locationEngine.removeLocationUpdates(locationEngineListener); + this.locationEngine.removeLocationUpdates(currentLocationEngineListener); this.locationEngine = null; } if (locationEngine != null) { this.locationEngine = locationEngine; + if (isLayerReady && isEnabled) { + setLastLocation(); + locationEngine.requestLocationUpdates( + locationEngineRequest, currentLocationEngineListener, Looper.getMainLooper()); + } } } + /** + * Set the location request that's going to be used when requesting location updates. + * + * @param locationEngineRequest the location request + */ + public void setLocationEngineRequest(@NonNull LocationEngineRequest locationEngineRequest) { + this.locationEngineRequest = locationEngineRequest; + + // reset internal LocationEngine ref to re-request location updates if needed + setLocationEngine(locationEngine); + } + + /** + * Get the location request that's going to be used when requesting location updates. + */ + @NonNull + public LocationEngineRequest getLocationEngineRequest() { + return locationEngineRequest; + } + /** * Returns the current {@link LocationEngine} being used for updating the user location. * @@ -546,31 +680,7 @@ public final class LocationComponent { @Nullable @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION}) public Location getLastKnownLocation() { - if (locationEngine == null) { - return null; - } - // TODO: decide if we want to change signature of this method, for now make it work as is - final AtomicReference resultRef = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - locationEngine.getLastLocation(new LocationEngineCallback() { - @Override - public void onSuccess(LocationEngineResult result) { - resultRef.set(result); - latch.countDown(); - } - - @Override - public void onFailure(@NonNull Exception exception) { - // TODO: log exception - } - }); - try { - latch.await(2, TimeUnit.SECONDS); - } catch (InterruptedException ie) { - ie.printStackTrace(); - } - LocationEngineResult result = resultRef.get(); - return result != null ? result.getLastLocation() : null; + return lastLocation; } /** @@ -750,13 +860,11 @@ public final class LocationComponent { if (isEnabled) { if (locationEngine != null) { - if (usingInternalLocationEngine) { - try { - locationEngine.requestLocationUpdates(getLocationRequst(DEFAULT_INTERVAL_MILLIS), - locationEngineListener, Looper.getMainLooper()); - } catch (SecurityException se) { - se.printStackTrace(); - } + try { + locationEngine.requestLocationUpdates( + locationEngineRequest, currentLocationEngineListener, Looper.getMainLooper()); + } catch (SecurityException se) { + Logger.e(TAG, "Unable to request location updates", se); } } setCameraMode(locationCameraController.getCameraMode()); @@ -776,9 +884,7 @@ public final class LocationComponent { compassEngine.onStop(); locationAnimatorCoordinator.cancelAllAnimations(); if (locationEngine != null) { - if (usingInternalLocationEngine) { - locationEngine.removeLocationUpdates(locationEngineListener); - } + locationEngine.removeLocationUpdates(currentLocationEngineListener); } mapboxMap.removeOnCameraMoveListener(onCameraMoveListener); mapboxMap.removeOnCameraIdleListener(onCameraIdleListener); @@ -823,17 +929,9 @@ public final class LocationComponent { private void initializeLocationEngine(@NonNull Context context) { if (this.locationEngine != null) { - this.locationEngine.removeLocationUpdates(locationEngineListener); + this.locationEngine.removeLocationUpdates(currentLocationEngineListener); } - usingInternalLocationEngine = true; - locationEngine = LocationEngineProvider.getBestLocationEngine(context, false); - } - - private static LocationEngineRequest getLocationRequst(long interval) { - return new LocationEngineRequest.Builder(interval) - .setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS) - .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY) - .build(); + locationEngine = internalLocationEngineProvider.getBestLocationEngine(context, false); } private void enableLocationComponent() { @@ -846,10 +944,13 @@ public final class LocationComponent { onLocationLayerStop(); } - private void updateMapWithOptions(final LocationComponentOptions options) { - mapboxMap.setPadding( - options.padding()[0], options.padding()[1], options.padding()[2], options.padding()[3] - ); + private void updateMapWithOptions(@NonNull LocationComponentOptions options) { + int[] padding = options.padding(); + if (padding != null) { + mapboxMap.setPadding( + padding[0], padding[1], padding[2], padding[3] + ); + } } /** @@ -894,7 +995,11 @@ public final class LocationComponent { */ @SuppressLint("MissingPermission") private void setLastLocation() { - updateLocation(getLastKnownLocation(), true); + if (locationEngine != null) { + locationEngine.getLastLocation(lastLocationEngineListener); + } else { + updateLocation(getLastKnownLocation(), true); + } } private void setLastCompassHeading() { @@ -1005,22 +1110,49 @@ public final class LocationComponent { } }; - @NonNull - private LocationEngineCallback locationEngineListener = - new LocationEngineCallback() { + @VisibleForTesting + static final class CurrentLocationEngineCallback implements LocationEngineCallback { + private final WeakReference componentWeakReference; + + CurrentLocationEngineCallback(LocationComponent component) { + this.componentWeakReference = new WeakReference<>(component); + } + @Override public void onSuccess(LocationEngineResult result) { - Location location = result.getLastLocation(); - if (location != null) { - updateLocation(location, false); + LocationComponent component = componentWeakReference.get(); + if (component != null) { + component.updateLocation(result.getLastLocation(), false); } } @Override public void onFailure(@NonNull Exception exception) { - // TODO: handle error + Logger.e(TAG, "Failed to obtain location update", exception); + } + } + + @VisibleForTesting + static final class LastLocationEngineCallback implements LocationEngineCallback { + private final WeakReference componentWeakReference; + + LastLocationEngineCallback(LocationComponent component) { + this.componentWeakReference = new WeakReference<>(component); } - }; + + @Override + public void onSuccess(LocationEngineResult result) { + LocationComponent component = componentWeakReference.get(); + if (component != null) { + component.updateLocation(result.getLastLocation(), true); + } + } + + @Override + public void onFailure(@NonNull Exception exception) { + Logger.e(TAG, "Failed to obtain last location update", exception); + } + } @NonNull private OnCameraTrackingChangedListener cameraTrackingChangedListener = new OnCameraTrackingChangedListener() { @@ -1040,4 +1172,10 @@ public final class LocationComponent { } } }; + + static class InternalLocationEngineProvider { + LocationEngine getBestLocationEngine(@NonNull Context context, boolean background) { + return LocationEngineProvider.getBestLocationEngine(context, background); + } + } } \ No newline at end of file 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..29fe413a22 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 @@ -26,6 +26,12 @@ final class LocationComponentConstants { // Threshold value to perform immediate camera/layer position update. static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 500_000; + // Default interval between location updates + static final long DEFAULT_INTERVAL_MILLIS = 1000; + + // Default fastest acceptable interval between location updates + static final long DEFAULT_FASTEST_INTERVAL_MILLIS = 1000; + // Sources static final String LOCATION_SOURCE = "mapbox-location-source"; static final String PROPERTY_GPS_BEARING = "mapbox-property-gps-bearing"; 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 25666895aa..db86db925c 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 @@ -187,7 +187,7 @@ public class LocationComponentOptions implements Parcelable { * @return a new {@link LocationComponentOptions} object with the settings you defined in your style * resource */ - @Nullable + @NonNull public static LocationComponentOptions createFromAttributes(@NonNull Context context, @StyleRes int styleRes) { -- cgit v1.2.1