summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java')
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java286
1 files changed, 212 insertions, 74 deletions
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
* <p>
* 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.
* <p>
* 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.
+ * <p>
+ * 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)}.
* <p>
* 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<LocationEngineResult> currentLocationEngineListener
+ = new CurrentLocationEngineCallback(this);
+ private LocationEngineCallback<LocationEngineResult> 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<LocationEngineResult> currentlistener,
+ @NonNull LocationEngineCallback<LocationEngineResult> 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));
}
/**
@@ -178,6 +219,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)}.
* <p>
* <strong>Note</strong>: This method will initialize and use an internal {@link LocationEngine} when enabled.
*
@@ -214,6 +275,7 @@ 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,
@@ -221,22 +283,53 @@ 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 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)}.
*
* @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);
@@ -245,6 +338,24 @@ 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 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.
*
* @param isEnabled true if the plugin should be visible and listen for location updates, false otherwise.
@@ -491,23 +602,46 @@ 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.
*
* @return the {@link LocationEngine} being used to update 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<LocationEngineResult> resultRef = new AtomicReference<>();
- CountDownLatch latch = new CountDownLatch(1);
- locationEngine.getLastLocation(new LocationEngineCallback<LocationEngineResult>() {
- @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<LocationEngineResult> locationEngineListener =
- new LocationEngineCallback<LocationEngineResult>() {
+ @VisibleForTesting
+ static final class CurrentLocationEngineCallback implements LocationEngineCallback<LocationEngineResult> {
+ private final WeakReference<LocationComponent> 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<LocationEngineResult> {
+ private final WeakReference<LocationComponent> 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