[android] refactor LocationComponent to utilize LocationEngineRequest

This commit is contained in:
Łukasz Paczos
2018-11-12 17:11:51 +01:00
committed by Łukasz Paczos
parent 2c70f4b6dc
commit 216b0d8a93
10 changed files with 420 additions and 223 deletions

View File

@@ -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));
}
/**
@@ -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<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);
}
}
}

View File

@@ -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";

View File

@@ -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) {