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.java1000
1 files changed, 1000 insertions, 0 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
new file mode 100644
index 0000000000..01ef314bf4
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java
@@ -0,0 +1,1000 @@
+package com.mapbox.mapboxsdk.location;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresPermission;
+import android.support.annotation.StyleRes;
+import android.support.v7.app.AppCompatDelegate;
+import android.view.WindowManager;
+
+import com.mapbox.android.core.location.LocationEngine;
+import com.mapbox.android.core.location.LocationEngineListener;
+import com.mapbox.android.core.location.LocationEnginePriority;
+import com.mapbox.android.core.location.LocationEngineProvider;
+import com.mapbox.mapboxsdk.R;
+import com.mapbox.mapboxsdk.camera.CameraPosition;
+import com.mapbox.mapboxsdk.camera.CameraUpdate;
+import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.location.modes.CameraMode;
+import com.mapbox.mapboxsdk.location.modes.RenderMode;
+import com.mapbox.mapboxsdk.log.Logger;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener;
+import com.mapbox.mapboxsdk.maps.MapboxMap.OnMapClickListener;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_TILT_ANIM_DURATION;
+import static com.mapbox.mapboxsdk.location.LocationComponentConstants.DEFAULT_TRACKING_ZOOM_ANIM_DURATION;
+
+/**
+ * The Location Component provides location awareness to your mobile application. Enabling this
+ * component provides a contextual experience to your users by showing an icon representing the users
+ * current location. A few different modes are offered to provide the right context to your users at
+ * the correct time. {@link RenderMode#NORMAL} simply shows the users location on the map
+ * represented as a dot. {@link RenderMode#COMPASS} mode allows you to display an arrow icon
+ * (by default) that points in the direction the device is pointing in.
+ * {@link RenderMode#GPS} can be used in conjunction with our Navigation SDK to
+ * display a larger icon (customized with {@link LocationComponentOptions#gpsDrawable()}) we call the user puck.
+ * <p>
+ * This component also offers the ability to set a map camera behavior for tracking the user
+ * location. These different {@link CameraMode}s will track, stop tracking the location based on the
+ * mode set with {@link LocationComponent#setCameraMode(int)}.
+ * <p>
+ * <strong>
+ * To get the component object use {@link MapboxMap#getLocationComponent()} and activate it with
+ * {@link #activateLocationComponent(Context)} or one of the overloads.
+ * Then, manage its visibility with {@link #setLocationComponentEnabled(boolean)}.
+ * </strong>
+ * <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.
+ * <p>
+ * This component offers a default, built-in {@link LocationEngine} with some of the activation methods.
+ * This engine will be obtained by {@link LocationEngineProvider#obtainBestLocationEngineAvailable} which defaults
+ * to the {@link com.mapbox.android.core.location.AndroidLocationEngine}. 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.GoogleLocationEngine} instead.
+ * <p>
+ * When activating the component for the first time, the map's max/min zoom levels will be set to
+ * {@link LocationComponentOptions#MAX_ZOOM_DEFAULT} and {@link LocationComponentOptions#MIN_ZOOM_DEFAULT} respectively.
+ * You can adjust the zoom range with {@link LocationComponentOptions#maxZoom()} and
+ * {@link LocationComponentOptions#minZoom()}.
+ * <p>
+ * Location Component doesn't support state saving out-of-the-box.
+ */
+public final class LocationComponent {
+ private static final String TAG = "Mbgl-LocationComponent";
+
+ private final MapboxMap mapboxMap;
+ private LocationComponentOptions options;
+ private LocationEngine locationEngine;
+ private CompassEngine compassEngine;
+ private boolean usingInternalLocationEngine;
+
+ private LocationLayerController locationLayerController;
+ private LocationCameraController locationCameraController;
+
+ private LocationAnimatorCoordinator locationAnimatorCoordinator;
+
+ /**
+ * Holds last location which is being returned in the {@link #getLastKnownLocation()}
+ * when there is no {@link #locationEngine} set or when the last location returned by the engine is null.
+ */
+ private Location lastLocation;
+ private CameraPosition lastCameraPosition;
+
+ /**
+ * Indicates whether the component has been initialized.
+ */
+ private boolean isInitialized;
+
+ /**
+ * Indicates that the component is enabled and should be displaying location if Mapbox components are available and
+ * the lifecycle is in a started state.
+ */
+ private boolean isEnabled;
+
+ /**
+ * Indicated that component's lifecycle {@link #onStart()} method has been called.
+ * This allows Mapbox components enter started state and display data, and adds state safety for methods like
+ * {@link #setLocationComponentEnabled(boolean)}
+ */
+ private boolean isComponentStarted;
+
+ /**
+ * Indicates if Mapbox components are ready to be interacted with. This can differ from {@link #isComponentStarted}
+ * if the Mapbox style is being reloaded.
+ */
+ private boolean isLayerReady;
+
+ private StaleStateManager staleStateManager;
+ private final CopyOnWriteArrayList<OnLocationStaleListener> onLocationStaleListeners
+ = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<OnLocationClickListener> onLocationClickListeners
+ = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<OnLocationLongClickListener> onLocationLongClickListeners
+ = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<OnCameraTrackingChangedListener> onCameraTrackingChangedListeners
+ = new CopyOnWriteArrayList<>();
+
+ /**
+ * Internal use.
+ * <p>
+ * To get the component object use {@link MapboxMap#getLocationComponent()}.
+ */
+ public LocationComponent(@NonNull MapboxMap mapboxMap) {
+ this.mapboxMap = mapboxMap;
+ }
+
+ /**
+ * 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.
+ *
+ * @param context the context
+ */
+ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
+ public void activateLocationComponent(@NonNull Context context) {
+ activateLocationComponent(context, LocationComponentOptions.createFromAttributes(context, 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 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
+ */
+ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
+ public void activateLocationComponent(@NonNull Context context, boolean useDefaultLocationEngine) {
+ 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.
+ *
+ * @param context the context
+ * @param styleRes the LocationComponent style res
+ */
+ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
+ public void activateLocationComponent(@NonNull Context context, @StyleRes int styleRes) {
+ activateLocationComponent(context, 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)}.
+ * <p>
+ * <strong>Note</strong>: This method will initialize and use an internal {@link LocationEngine} when enabled.
+ * </p>
+ *
+ * @param context the context
+ * @param options the options
+ */
+ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
+ public void activateLocationComponent(@NonNull Context context, @NonNull LocationComponentOptions options) {
+ initialize(context, options);
+ initializeLocationEngine(context);
+ 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 styleRes the LocationComponent style res
+ */
+ public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine,
+ @StyleRes int styleRes) {
+ activateLocationComponent(context, locationEngine,
+ 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
+ */
+ public void activateLocationComponent(@NonNull Context context, @NonNull LocationEngine locationEngine) {
+ activateLocationComponent(context, locationEngine, 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
+ */
+ public void activateLocationComponent(@NonNull Context context, @Nullable LocationEngine locationEngine,
+ @NonNull LocationComponentOptions options) {
+ initialize(context, options);
+ 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.
+ */
+ public void setLocationComponentEnabled(boolean isEnabled) {
+ if (isEnabled) {
+ enableLocationComponent();
+ } else {
+ disableLocationComponent();
+ }
+ }
+
+ /**
+ * Returns whether the plugin is enabled, meaning that location can be displayed and camera modes can be used.
+ *
+ * @return true if the plugin is enabled, false otherwise
+ */
+ public boolean isLocationComponentEnabled() {
+ return isEnabled;
+ }
+
+ /**
+ * Sets the camera mode, which determines how the map camera will track the rendered location.
+ * <p>
+ * <ul>
+ * <li>{@link CameraMode#NONE}: No camera tracking</li>
+ * <li>{@link CameraMode#NONE_COMPASS}: Camera does not track location, but does track compass bearing</li>
+ * <li>{@link CameraMode#NONE_GPS}: Camera does not track location, but does track GPS bearing</li>
+ * <li>{@link CameraMode#TRACKING}: Camera tracks the user location</li>
+ * <li>{@link CameraMode#TRACKING_COMPASS}: Camera tracks the user location, with bearing provided by a compass</li>
+ * <li>{@link CameraMode#TRACKING_GPS}: Camera tracks the user location, with normalized bearing</li>
+ * <li>{@link CameraMode#TRACKING_GPS_NORTH}: Camera tracks the user location, with bearing always set to north</li>
+ * </ul>
+ *
+ * @param cameraMode one of the modes found in {@link CameraMode}
+ */
+ public void setCameraMode(@CameraMode.Mode int cameraMode) {
+ locationCameraController.setCameraMode(cameraMode);
+ boolean isGpsNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH;
+ locationAnimatorCoordinator.resetAllCameraAnimations(mapboxMap.getCameraPosition(), isGpsNorth);
+ }
+
+ /**
+ * Provides the current camera mode being used to track the location or compass updates.
+ *
+ * @return the current camera mode
+ */
+ @CameraMode.Mode
+ public int getCameraMode() {
+ return locationCameraController.getCameraMode();
+ }
+
+ /**
+ * Sets the render mode, which determines how the location updates will be rendered on the map.
+ * <p>
+ * <ul>
+ * <li>{@link RenderMode#NORMAL}: Shows user location, bearing ignored</li>
+ * <li>{@link RenderMode#COMPASS}: Shows user location with bearing considered from compass</li>
+ * <li>{@link RenderMode#GPS}: Shows user location with bearing considered from location</li>
+ * </ul>
+ *
+ * @param renderMode one of the modes found in {@link RenderMode}
+ */
+ public void setRenderMode(@RenderMode.Mode int renderMode) {
+ locationLayerController.setRenderMode(renderMode);
+ updateLayerOffsets(true);
+ }
+
+ /**
+ * Provides the current render mode being used to show
+ * the location and/or compass updates on the map.
+ *
+ * @return the current render mode
+ */
+ @RenderMode.Mode
+ public int getRenderMode() {
+ return locationLayerController.getRenderMode();
+ }
+
+ /**
+ * Returns the current location options being used.
+ *
+ * @return the current {@link LocationComponentOptions}
+ */
+ public LocationComponentOptions getLocationComponentOptions() {
+ return options;
+ }
+
+ /**
+ * Apply a new component style with a style resource.
+ *
+ * @param styleRes a XML style overriding some or all the options
+ */
+ public void applyStyle(@NonNull Context context, @StyleRes int styleRes) {
+ applyStyle(LocationComponentOptions.createFromAttributes(context, styleRes));
+ }
+
+ /**
+ * Apply a new component style with location component options.
+ *
+ * @param options to update the current style
+ */
+ public void applyStyle(LocationComponentOptions options) {
+ this.options = options;
+ locationLayerController.applyStyle(options);
+ locationCameraController.initializeOptions(options);
+ staleStateManager.setEnabled(options.enableStaleState());
+ staleStateManager.setDelayTime(options.staleStateTimeout());
+ updateMapWithOptions(options);
+ }
+
+ /**
+ * Zooms to the desired zoom level.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param zoomLevel The desired zoom level.
+ * @param animationDuration The zoom animation duration.
+ * @param callback The callback with finish/cancel information
+ */
+ public void zoomWhileTracking(double zoomLevel, long animationDuration,
+ @Nullable MapboxMap.CancelableCallback callback) {
+ if (!isLayerReady) {
+ return;
+ } else if (getCameraMode() == CameraMode.NONE) {
+ Logger.e(TAG, String.format("%s%s",
+ "LocationComponent#zoomWhileTracking method can only be used",
+ " when a camera mode other than CameraMode#NONE is engaged."));
+ return;
+ }
+ locationAnimatorCoordinator.feedNewZoomLevel(zoomLevel, mapboxMap.getCameraPosition(), animationDuration, callback);
+ }
+
+ /**
+ * Zooms to the desired zoom level.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param zoomLevel The desired zoom level.
+ * @param animationDuration The zoom animation duration.
+ */
+ public void zoomWhileTracking(double zoomLevel, long animationDuration) {
+ zoomWhileTracking(zoomLevel, animationDuration, null);
+ }
+
+ /**
+ * Zooms to the desired zoom level.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param zoomLevel The desired zoom level.
+ */
+ public void zoomWhileTracking(double zoomLevel) {
+ zoomWhileTracking(zoomLevel, DEFAULT_TRACKING_ZOOM_ANIM_DURATION, null);
+ }
+
+ /**
+ * Cancels animation started by {@link #zoomWhileTracking(double, long, MapboxMap.CancelableCallback)}.
+ */
+ public void cancelZoomWhileTrackingAnimation() {
+ locationAnimatorCoordinator.cancelZoomAnimation();
+ }
+
+ /**
+ * Tilts the camera.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param tilt The desired camera tilt.
+ * @param animationDuration The tilt animation duration.
+ * @param callback The callback with finish/cancel information
+ */
+ public void tiltWhileTracking(double tilt, long animationDuration,
+ @Nullable MapboxMap.CancelableCallback callback) {
+ if (!isLayerReady) {
+ return;
+ } else if (getCameraMode() == CameraMode.NONE) {
+ Logger.e(TAG, String.format("%s%s",
+ "LocationComponent#tiltWhileTracking method can only be used",
+ " when a camera mode other than CameraMode#NONE is engaged."));
+ return;
+ }
+ locationAnimatorCoordinator.feedNewTilt(tilt, mapboxMap.getCameraPosition(), animationDuration, callback);
+ }
+
+ /**
+ * Tilts the camera.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param tilt The desired camera tilt.
+ * @param animationDuration The tilt animation duration.
+ */
+ public void tiltWhileTracking(double tilt, long animationDuration) {
+ tiltWhileTracking(tilt, animationDuration, null);
+ }
+
+ /**
+ * Tilts the camera.
+ * This API can only be used in pair with camera modes other than {@link CameraMode#NONE}.
+ * If you are not using any of {@link CameraMode} modes,
+ * use one of {@link MapboxMap#moveCamera(CameraUpdate)},
+ * {@link MapboxMap#easeCamera(CameraUpdate)} or {@link MapboxMap#animateCamera(CameraUpdate)} instead.
+ *
+ * @param tilt The desired camera tilt.
+ */
+ public void tiltWhileTracking(double tilt) {
+ tiltWhileTracking(tilt, DEFAULT_TRACKING_TILT_ANIM_DURATION, null);
+ }
+
+ /**
+ * Cancels animation started by {@link #tiltWhileTracking(double, long, MapboxMap.CancelableCallback)}.
+ */
+ public void cancelTiltWhileTrackingAnimation() {
+ locationAnimatorCoordinator.cancelTiltAnimation();
+ }
+
+ /**
+ * Use to either force a location update or to manually control when the user location gets
+ * updated.
+ *
+ * @param location where the location icon is placed on the map
+ */
+ public void forceLocationUpdate(@Nullable Location location) {
+ updateLocation(location, false);
+ }
+
+ /**
+ * Set the location engine to update the current user location.
+ * <p>
+ * If {@code null} is passed in, all updates will have to occur through the
+ * {@link LocationComponent#forceLocationUpdate(Location)} method.
+ *
+ * @param locationEngine a {@link LocationEngine} this component should use to handle updates
+ */
+ 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) {
+ this.locationEngine.removeLocationUpdates();
+ this.locationEngine.deactivate();
+ usingInternalLocationEngine = false;
+ }
+ this.locationEngine.removeLocationEngineListener(locationEngineListener);
+ this.locationEngine = null;
+ }
+
+ if (locationEngine != null) {
+ this.locationEngine = locationEngine;
+ if (isEnabled) {
+ this.locationEngine.addLocationEngineListener(locationEngineListener);
+ }
+ }
+ }
+
+ /**
+ * Returns the current {@link LocationEngine} being used for updating the user location.
+ *
+ * @return the {@link LocationEngine} being used to update the user location
+ */
+ @Nullable
+ public LocationEngine getLocationEngine() {
+ return locationEngine;
+ }
+
+ /**
+ * Sets the compass engine used to provide compass heading values.
+ *
+ * @param compassEngine to be used
+ */
+ public void setCompassEngine(@NonNull CompassEngine compassEngine) {
+ this.compassEngine.removeCompassListener(compassListener);
+ this.compassEngine = compassEngine;
+ compassEngine.addCompassListener(compassListener);
+ }
+
+ /**
+ * Returns the compass engine used to provide compass heading values.
+ *
+ * @return compass engine currently being used
+ */
+ @NonNull
+ public CompassEngine getCompassEngine() {
+ return compassEngine;
+ }
+
+ /**
+ * Get the last know location of the location component.
+ *
+ * @return the last known location
+ */
+ @Nullable
+ @RequiresPermission(anyOf = {ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION})
+ public Location getLastKnownLocation() {
+ Location location = locationEngine != null ? locationEngine.getLastLocation() : null;
+ if (location == null) {
+ location = lastLocation;
+ }
+ return location;
+ }
+
+ /**
+ * Return the last known {@link CompassEngine} accuracy status of the location component.
+ * <p>
+ * The last known accuracy of the compass sensor, one of SensorManager.SENSOR_STATUS_*
+ *
+ * @return the last know compass accuracy bearing
+ */
+ public float getLastKnownCompassAccuracyStatus() {
+ return compassEngine.getLastAccuracySensorStatus();
+ }
+
+ /**
+ * Add a compass listener to get heading updates every second. Once the first listener gets added,
+ * the sensor gets initiated and starts returning values.
+ *
+ * @param compassListener a {@link CompassListener} for listening into compass heading and
+ * accuracy changes
+ */
+ public void addCompassListener(@NonNull CompassListener compassListener) {
+ compassEngine.addCompassListener(compassListener);
+ }
+
+ /**
+ * Remove a compass listener.
+ *
+ * @param compassListener the {@link CompassListener} which you'd like to remove from the listener
+ * list.
+ */
+ public void removeCompassListener(@NonNull CompassListener compassListener) {
+ compassEngine.removeCompassListener(compassListener);
+ }
+
+ /**
+ * Adds a listener that gets invoked when the user clicks the displayed location.
+ *
+ * @param listener The location click listener that is invoked when the
+ * location is clicked
+ */
+ public void addOnLocationClickListener(@NonNull OnLocationClickListener listener) {
+ onLocationClickListeners.add(listener);
+ }
+
+ /**
+ * Removes the passed listener from the current list of location click listeners.
+ *
+ * @param listener to be removed
+ */
+ public void removeOnLocationClickListener(@NonNull OnLocationClickListener listener) {
+ onLocationClickListeners.remove(listener);
+ }
+
+ /**
+ * Adds a listener that gets invoked when the user long clicks the displayed location.
+ *
+ * @param listener The location click listener that is invoked when the
+ * location is clicked
+ */
+ public void addOnLocationLongClickListener(@NonNull OnLocationLongClickListener listener) {
+ onLocationLongClickListeners.add(listener);
+ }
+
+ /**
+ * Removes the passed listener from the current list of location long click listeners.
+ *
+ * @param listener to be removed
+ */
+ public void removeOnLocationLongClickListener(@NonNull OnLocationLongClickListener listener) {
+ onLocationLongClickListeners.remove(listener);
+ }
+
+ /**
+ * Adds a listener that gets invoked when camera tracking state changes.
+ *
+ * @param listener Listener that gets invoked when camera tracking state changes.
+ */
+ public void addOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
+ onCameraTrackingChangedListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener that gets invoked when camera tracking state changes.
+ *
+ * @param listener Listener that gets invoked when camera tracking state changes.
+ */
+ public void removeOnCameraTrackingChangedListener(@NonNull OnCameraTrackingChangedListener listener) {
+ onCameraTrackingChangedListeners.remove(listener);
+ }
+
+ /**
+ * Adds the passed listener that gets invoked when user updates have stopped long enough for the last update
+ * to be considered stale.
+ * <p>
+ * This timeout is set by {@link LocationComponentOptions#staleStateTimeout()}.
+ *
+ * @param listener invoked when last update is considered stale
+ */
+ public void addOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
+ onLocationStaleListeners.add(listener);
+ }
+
+ /**
+ * Removes the passed listener from the current list of stale listeners.
+ *
+ * @param listener to be removed from the list
+ */
+ public void removeOnLocationStaleListener(@NonNull OnLocationStaleListener listener) {
+ onLocationStaleListeners.remove(listener);
+ }
+
+ /**
+ * Internal use.
+ */
+ public void onStart() {
+ isComponentStarted = true;
+ onLocationLayerStart();
+ }
+
+ /**
+ * Internal use.
+ */
+ public void onStop() {
+ onLocationLayerStop();
+ isComponentStarted = false;
+ }
+
+ /**
+ * Internal use.
+ */
+ public void onDestroy() {
+ if (locationEngine != null && usingInternalLocationEngine) {
+ locationEngine.deactivate();
+ }
+ }
+
+ /**
+ * Internal use.
+ */
+ public void onStartLoadingMap() {
+ onLocationLayerStop();
+ }
+
+ /**
+ * Internal use.
+ */
+ public void onFinishLoadingStyle() {
+ if (isInitialized) {
+ locationLayerController.initializeComponents(options);
+ locationCameraController.initializeOptions(options);
+ }
+ onLocationLayerStart();
+ }
+
+ @SuppressLint("MissingPermission")
+ private void onLocationLayerStart() {
+ if (!isInitialized || !isComponentStarted) {
+ return;
+ }
+
+ if (!isLayerReady) {
+ isLayerReady = true;
+ mapboxMap.addOnCameraMoveListener(onCameraMoveListener);
+ mapboxMap.addOnCameraIdleListener(onCameraIdleListener);
+ if (options.enableStaleState()) {
+ staleStateManager.onStart();
+ }
+ compassEngine.onStart();
+ }
+
+ if (isEnabled) {
+ if (locationEngine != null) {
+ locationEngine.addLocationEngineListener(locationEngineListener);
+ if (locationEngine.isConnected() && usingInternalLocationEngine) {
+ locationEngine.requestLocationUpdates();
+ }
+ }
+ setCameraMode(locationCameraController.getCameraMode());
+ setLastLocation();
+ setLastCompassHeading();
+ }
+ }
+
+ private void onLocationLayerStop() {
+ if (!isInitialized || !isLayerReady || !isComponentStarted) {
+ return;
+ }
+
+ isLayerReady = false;
+ locationLayerController.hide();
+ staleStateManager.onStop();
+ compassEngine.onStop();
+ locationAnimatorCoordinator.cancelAllAnimations();
+ if (locationEngine != null) {
+ if (usingInternalLocationEngine) {
+ locationEngine.removeLocationUpdates();
+ }
+ locationEngine.removeLocationEngineListener(locationEngineListener);
+ }
+ mapboxMap.removeOnCameraMoveListener(onCameraMoveListener);
+ mapboxMap.removeOnCameraIdleListener(onCameraIdleListener);
+ }
+
+ private void initialize(@NonNull Context context, @NonNull LocationComponentOptions options) {
+ if (isInitialized) {
+ return;
+ }
+ isInitialized = true;
+ this.options = options;
+
+ AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
+
+ mapboxMap.addOnMapClickListener(onMapClickListener);
+ mapboxMap.addOnMapLongClickListener(onMapLongClickListener);
+
+ LayerSourceProvider sourceProvider = new LayerSourceProvider();
+ LayerFeatureProvider featureProvider = new LayerFeatureProvider();
+ LayerBitmapProvider bitmapProvider = new LayerBitmapProvider(context);
+ locationLayerController = new LocationLayerController(mapboxMap, sourceProvider, featureProvider, bitmapProvider,
+ options);
+ locationCameraController = new LocationCameraController(
+ context, mapboxMap, cameraTrackingChangedListener, options, onCameraMoveInvalidateListener);
+ locationAnimatorCoordinator = new LocationAnimatorCoordinator();
+ locationAnimatorCoordinator.addLayerListener(locationLayerController);
+ locationAnimatorCoordinator.addCameraListener(locationCameraController);
+
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager);
+ compassEngine.addCompassListener(compassListener);
+ staleStateManager = new StaleStateManager(onLocationStaleListener, options);
+
+ updateMapWithOptions(options);
+
+ setRenderMode(RenderMode.NORMAL);
+ setCameraMode(CameraMode.NONE);
+
+ onLocationLayerStart();
+ }
+
+ private void initializeLocationEngine(@NonNull Context context) {
+ if (this.locationEngine != null) {
+ if (usingInternalLocationEngine) {
+ this.locationEngine.removeLocationUpdates();
+ this.locationEngine.deactivate();
+ }
+ this.locationEngine.removeLocationEngineListener(locationEngineListener);
+ }
+
+ usingInternalLocationEngine = true;
+ locationEngine = new LocationEngineProvider(context).obtainBestLocationEngineAvailable();
+ locationEngine.setPriority(LocationEnginePriority.HIGH_ACCURACY);
+ locationEngine.setFastestInterval(1000);
+ locationEngine.addLocationEngineListener(locationEngineListener);
+ locationEngine.activate();
+ }
+
+ private void enableLocationComponent() {
+ isEnabled = true;
+ onLocationLayerStart();
+ }
+
+ private void disableLocationComponent() {
+ isEnabled = false;
+ onLocationLayerStop();
+ }
+
+ private void updateMapWithOptions(final LocationComponentOptions options) {
+ mapboxMap.setPadding(
+ options.padding()[0], options.padding()[1], options.padding()[2], options.padding()[3]
+ );
+
+ mapboxMap.setMaxZoomPreference(options.maxZoom());
+ mapboxMap.setMinZoomPreference(options.minZoom());
+ }
+
+ /**
+ * Updates the user location icon.
+ *
+ * @param location the latest user location
+ */
+ private void updateLocation(final Location location, boolean fromLastLocation) {
+ if (location == null) {
+ return;
+ } else if (!isLayerReady) {
+ lastLocation = location;
+ return;
+ }
+
+ showLocationLayerIfHidden();
+
+ if (!fromLastLocation) {
+ staleStateManager.updateLatestLocationTime();
+ }
+ CameraPosition currentCameraPosition = mapboxMap.getCameraPosition();
+ boolean isGpsNorth = getCameraMode() == CameraMode.TRACKING_GPS_NORTH;
+ locationAnimatorCoordinator.feedNewLocation(location, currentCameraPosition, isGpsNorth);
+ updateAccuracyRadius(location, false);
+ lastLocation = location;
+ }
+
+ private void showLocationLayerIfHidden() {
+ boolean isLocationLayerHidden = locationLayerController.isHidden();
+ if (isEnabled && isComponentStarted && isLocationLayerHidden) {
+ locationLayerController.show();
+ }
+ }
+
+ private void updateCompassHeading(float heading) {
+ locationAnimatorCoordinator.feedNewCompassBearing(heading, mapboxMap.getCameraPosition());
+ }
+
+ /**
+ * If the locationEngine contains a last location value, we use it for the initial location layer
+ * position.
+ */
+ @SuppressLint("MissingPermission")
+ private void setLastLocation() {
+ updateLocation(getLastKnownLocation(), true);
+ }
+
+ private void setLastCompassHeading() {
+ updateCompassHeading(compassEngine.getLastHeading());
+ }
+
+ @SuppressLint("MissingPermission")
+ private void updateLayerOffsets(boolean forceUpdate) {
+ CameraPosition position = mapboxMap.getCameraPosition();
+ if (lastCameraPosition == null || forceUpdate) {
+ lastCameraPosition = position;
+ locationLayerController.updateForegroundBearing((float) position.bearing);
+ locationLayerController.updateForegroundOffset(position.tilt);
+ updateAccuracyRadius(getLastKnownLocation(), true);
+ return;
+ }
+
+ if (position.bearing != lastCameraPosition.bearing) {
+ locationLayerController.updateForegroundBearing((float) position.bearing);
+ }
+ if (position.tilt != lastCameraPosition.tilt) {
+ locationLayerController.updateForegroundOffset(position.tilt);
+ }
+ if (position.zoom != lastCameraPosition.zoom) {
+ updateAccuracyRadius(getLastKnownLocation(), true);
+ }
+ lastCameraPosition = position;
+ }
+
+ private void updateAccuracyRadius(Location location, boolean noAnimation) {
+ locationAnimatorCoordinator.feedNewAccuracyRadius(Utils.calculateZoomLevelRadius(mapboxMap, location), noAnimation);
+ }
+
+ private OnCameraMoveListener onCameraMoveListener = new OnCameraMoveListener() {
+ @Override
+ public void onCameraMove() {
+ updateLayerOffsets(false);
+ }
+ };
+
+ private OnCameraIdleListener onCameraIdleListener = new OnCameraIdleListener() {
+ @Override
+ public void onCameraIdle() {
+ updateLayerOffsets(false);
+ }
+ };
+
+ private OnMapClickListener onMapClickListener = new OnMapClickListener() {
+ @Override
+ public void onMapClick(@NonNull LatLng point) {
+ if (!onLocationClickListeners.isEmpty() && locationLayerController.onMapClick(point)) {
+ for (OnLocationClickListener listener : onLocationClickListeners) {
+ listener.onLocationComponentClick();
+ }
+ }
+ }
+ };
+
+ private MapboxMap.OnMapLongClickListener onMapLongClickListener = new MapboxMap.OnMapLongClickListener() {
+ @Override
+ public void onMapLongClick(@NonNull LatLng point) {
+ if (!onLocationLongClickListeners.isEmpty() && locationLayerController.onMapClick(point)) {
+ for (OnLocationLongClickListener listener : onLocationLongClickListeners) {
+ listener.onLocationComponentLongClick();
+ }
+ }
+ }
+ };
+
+ private OnLocationStaleListener onLocationStaleListener = new OnLocationStaleListener() {
+ @Override
+ public void onStaleStateChange(boolean isStale) {
+ locationLayerController.setLocationsStale(isStale);
+
+ for (OnLocationStaleListener listener : onLocationStaleListeners) {
+ listener.onStaleStateChange(isStale);
+ }
+ }
+ };
+
+ private OnCameraMoveInvalidateListener onCameraMoveInvalidateListener = new OnCameraMoveInvalidateListener() {
+ @Override
+ public void onInvalidateCameraMove() {
+ onCameraMoveListener.onCameraMove();
+ }
+ };
+
+ private CompassListener compassListener = new CompassListener() {
+ @Override
+ public void onCompassChanged(float userHeading) {
+ updateCompassHeading(userHeading);
+ }
+
+ @Override
+ public void onCompassAccuracyChange(int compassStatus) {
+ // Currently don't handle this inside SDK
+ }
+ };
+
+ private LocationEngineListener locationEngineListener = new LocationEngineListener() {
+ @Override
+ @SuppressWarnings( {"MissingPermission"})
+ public void onConnected() {
+ if (usingInternalLocationEngine && isLayerReady && isEnabled) {
+ locationEngine.requestLocationUpdates();
+ }
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ updateLocation(location, false);
+ }
+ };
+
+ private OnCameraTrackingChangedListener cameraTrackingChangedListener = new OnCameraTrackingChangedListener() {
+ @Override
+ public void onCameraTrackingDismissed() {
+ for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) {
+ listener.onCameraTrackingDismissed();
+ }
+ }
+
+ @Override
+ public void onCameraTrackingChanged(int currentMode) {
+ locationAnimatorCoordinator.cancelZoomAnimation();
+ locationAnimatorCoordinator.cancelTiltAnimation();
+ for (OnCameraTrackingChangedListener listener : onCameraTrackingChangedListeners) {
+ listener.onCameraTrackingChanged(currentMode);
+ }
+ }
+ };
+}