diff --git a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationLauncher.java b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationLauncher.java index 5478abd316d..b32c67580d0 100644 --- a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationLauncher.java +++ b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationLauncher.java @@ -10,13 +10,7 @@ import com.google.gson.GsonBuilder; import com.mapbox.api.directions.v5.DirectionsAdapterFactory; import com.mapbox.api.directions.v5.models.DirectionsRoute; -import com.mapbox.geojson.BoundingBox; -import com.mapbox.geojson.Geometry; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; import com.mapbox.services.android.navigation.v5.navigation.NavigationConstants; import java.util.HashMap; @@ -75,17 +69,7 @@ public static void startNavigation(Activity activity, NavigationViewOptions opti static DirectionsRoute extractRoute(Context context) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String directionsRouteJson = preferences.getString(NavigationConstants.NAVIGATION_VIEW_ROUTE_KEY, ""); - return directionsRouteFromJson(directionsRouteJson); - } - - private static DirectionsRoute directionsRouteFromJson(String json) { - GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); - gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create()); - return gson.create().fromJson(json, DirectionsRoute.class); + return DirectionsRoute.fromJson(directionsRouteJson); } /** diff --git a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewModel.java b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewModel.java index 250156022d3..2dbe5ad5ab3 100644 --- a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewModel.java +++ b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewModel.java @@ -30,6 +30,7 @@ import com.mapbox.services.android.navigation.v5.navigation.NavigationEventListener; import com.mapbox.services.android.navigation.v5.navigation.metrics.FeedbackEvent; import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener; +import com.mapbox.services.android.navigation.v5.route.FasterRouteListener; import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; import com.mapbox.services.android.telemetry.location.LocationEngine; @@ -37,7 +38,7 @@ import java.text.DecimalFormat; public class NavigationViewModel extends AndroidViewModel implements ProgressChangeListener, - OffRouteListener, MilestoneEventListener, NavigationEventListener { + OffRouteListener, MilestoneEventListener, NavigationEventListener, FasterRouteListener { public final MutableLiveData instructionModel = new MutableLiveData<>(); public final MutableLiveData bannerInstructionModel = new MutableLiveData<>(); @@ -46,6 +47,7 @@ public class NavigationViewModel extends AndroidViewModel implements ProgressCha public final MutableLiveData isFeedbackShowing = new MutableLiveData<>(); final MutableLiveData selectedFeedbackItem = new MutableLiveData<>(); final MutableLiveData navigationLocation = new MutableLiveData<>(); + final MutableLiveData fasterRoute = new MutableLiveData<>(); final MutableLiveData newOrigin = new MutableLiveData<>(); final MutableLiveData isRunning = new MutableLiveData<>(); final MutableLiveData shouldRecordScreenshot = new MutableLiveData<>(); @@ -144,6 +146,18 @@ public void onRunning(boolean running) { isRunning.setValue(running); } + /** + * Listener that will be fired if a faster {@link DirectionsRoute} is found + * while navigating. + * + * @param directionsRoute faster route retrieved + * @since 0.9.0 + */ + @Override + public void fasterRouteFound(DirectionsRoute directionsRoute) { + fasterRoute.setValue(directionsRoute); + } + public void setMuted(boolean isMuted) { instructionPlayer.setMuted(isMuted); } @@ -266,6 +280,7 @@ private void addNavigationListeners() { navigation.addOffRouteListener(this); navigation.addMilestoneEventListener(this); navigation.addNavigationEventListener(this); + navigation.addFasterRouteListener(this); } } diff --git a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewSubscriber.java b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewSubscriber.java index e208204d829..ab6e56baf33 100644 --- a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewSubscriber.java +++ b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/NavigationViewSubscriber.java @@ -62,13 +62,14 @@ public void onChanged(@Nullable String requestErrorMessage) { routeViewModel.route.observe(owner, new Observer() { @Override public void onChanged(@Nullable DirectionsRoute directionsRoute) { - if (isOffRoute) { - navigationViewEventDispatcher.onRerouteAlong(directionsRoute); - } if (directionsRoute != null) { navigationViewModel.updateRoute(directionsRoute); locationViewModel.updateRoute(directionsRoute); navigationPresenter.onRouteUpdate(directionsRoute); + + if (isOffRoute) { + navigationViewEventDispatcher.onRerouteAlong(directionsRoute); + } } } }); @@ -104,6 +105,19 @@ public void onChanged(@Nullable Location location) { } }); + navigationViewModel.fasterRoute.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable DirectionsRoute directionsRoute) { + if (directionsRoute != null) { + navigationViewModel.updateRoute(directionsRoute); + locationViewModel.updateRoute(directionsRoute); + navigationPresenter.onRouteUpdate(directionsRoute); + // To prevent from firing on rotation + navigationViewModel.fasterRoute.setValue(null); + } + } + }); + navigationViewModel.newOrigin.observe(owner, new Observer() { @Override public void onChanged(@Nullable Point newOrigin) { diff --git a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/route/RouteViewModel.java b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/route/RouteViewModel.java index f31de3aad3c..5837493988c 100644 --- a/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/route/RouteViewModel.java +++ b/libandroid-navigation-ui/src/main/java/com/mapbox/services/android/navigation/ui/v5/route/RouteViewModel.java @@ -5,6 +5,7 @@ import android.arch.lifecycle.MutableLiveData; import android.location.Location; import android.support.annotation.NonNull; +import android.text.TextUtils; import com.mapbox.api.directions.v5.DirectionsCriteria; import com.mapbox.api.directions.v5.models.DirectionsResponse; @@ -203,8 +204,13 @@ private void cacheRouteProfile(NavigationViewOptions options, DirectionsRoute ro * @param route as backup if view options language not found */ private void cacheRouteLanguage(NavigationViewOptions options, DirectionsRoute route) { - Locale language = options.directionsLanguage(); - this.language = language != null ? language : new Locale(route.routeOptions().language()); + if (options.directionsLanguage() != null) { + language = options.directionsLanguage(); + } else if (!TextUtils.isEmpty(route.routeOptions().language())) { + language = new Locale(route.routeOptions().language()); + } else { + language = Locale.getDefault(); + } } /** diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigation.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigation.java index 2db0f1b4e94..25c9a4f1b3b 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigation.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigation.java @@ -18,6 +18,9 @@ import com.mapbox.services.android.navigation.v5.offroute.OffRoute; import com.mapbox.services.android.navigation.v5.offroute.OffRouteDetector; import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener; +import com.mapbox.services.android.navigation.v5.route.FasterRoute; +import com.mapbox.services.android.navigation.v5.route.FasterRouteDetector; +import com.mapbox.services.android.navigation.v5.route.FasterRouteListener; import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener; import com.mapbox.services.android.navigation.v5.snap.Snap; import com.mapbox.services.android.navigation.v5.snap.SnapToRoute; @@ -53,6 +56,7 @@ public class MapboxNavigation implements ServiceConnection { private List milestones; private final String accessToken; private OffRoute offRouteEngine; + private FasterRoute fasterRouteEngine; private Snap snapEngine; private Context context; private boolean isBound; @@ -143,13 +147,15 @@ private void initialize() { addMilestone(new VoiceInstructionMilestone.Builder().setIdentifier(VOICE_INSTRUCTION_MILESTONE_ID).build()); addMilestone(new BannerInstructionMilestone.Builder().setIdentifier(BANNER_INSTRUCTION_MILESTONE_ID).build()); } - if (options.snapToRoute()) { snapEngine = new SnapToRoute(); } if (options.enableOffRouteDetection()) { offRouteEngine = new OffRouteDetector(); } + if (options().enableFasterRouteDetection()) { + fasterRouteEngine = new FasterRouteDetector(); + } } private void initializeTelemetry() { @@ -546,6 +552,44 @@ public void removeNavigationEventListener(@Nullable NavigationEventListener navi navigationEventDispatcher.removeNavigationEventListener(navigationEventListener); } + /** + * This adds a new faster route listener which is invoked when a new, faster {@link DirectionsRoute} + * has been retrieved by the specified criteria in {@link FasterRoute}. + *

+ * The behavior that causes this listeners callback to get invoked vary depending on whether a + * custom faster route engine has been set using {@link #setFasterRouteEngine(FasterRoute)}. + *

+ * It is not possible to add the same listener implementation more then once and a warning will be + * printed in the log if attempted. + *

+ * + * @param fasterRouteListener an implementation of {@code FasterRouteListener} + * @see FasterRouteListener + * @since 0.9.0 + */ + public void addFasterRouteListener(@NonNull FasterRouteListener fasterRouteListener) { + navigationEventDispatcher.addFasterRouteListener(fasterRouteListener); + } + + /** + * This removes a specific faster route listener by passing in the instance of it or you can pass in + * null to remove all the listeners. When {@link #onDestroy()} is called, all listeners + * get removed automatically, removing the requirement for developers to manually handle this. + *

+ * If the listener you are trying to remove does not exist in the list, a warning will be printed + * in the log. + *

+ * + * @param fasterRouteListener an implementation of {@code FasterRouteListener} which currently exist in + * the fasterRouteListeners list + * @see FasterRouteListener + * @since 0.9.0 + */ + @SuppressWarnings("WeakerAccess") // Public exposed for usage outside SDK + public void removeFasterRouteListener(@Nullable FasterRouteListener fasterRouteListener) { + navigationEventDispatcher.removeFasterRouteListener(fasterRouteListener); + } + // Custom engines /** @@ -614,6 +658,38 @@ public OffRoute getOffRouteEngine() { return offRouteEngine; } + /** + * This API is used to pass in a custom implementation of the faster-route detection logic, A default + * faster-route detection engine is attached when this class is first initialized; setting a custom + * one will replace it with your own implementation. + *

+ * The engine can be changed at anytime, even during a navigation session. + *

+ * + * @param fasterRouteEngine a custom implementation of the {@link FasterRoute} class + * @see FasterRoute + * @since 0.9.0 + */ + @SuppressWarnings("WeakerAccess") // Public exposed for usage outside SDK + public void setFasterRouteEngine(@NonNull FasterRoute fasterRouteEngine) { + this.fasterRouteEngine = fasterRouteEngine; + } + + /** + * This will return the currently set faster-route engine which will or is being used during the + * navigation session. If no faster-route engine has been set yet, the default engine will be + * returned. + * + * @return the faster-route engine currently set and will/is being used for the navigation session + * @see FasterRoute + * @since 0.9.0 + */ + @SuppressWarnings("WeakerAccess") // Public exposed for usage outside SDK + @NonNull + public FasterRoute getFasterRouteEngine() { + return fasterRouteEngine; + } + /** * Creates a new {@link FeedbackEvent} with a given type, description, and source. *

diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigationOptions.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigationOptions.java index 488581b1cf2..426611a12cf 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigationOptions.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/MapboxNavigationOptions.java @@ -31,6 +31,8 @@ public abstract class MapboxNavigationOptions { public abstract boolean enableOffRouteDetection(); + public abstract boolean enableFasterRouteDetection(); + public abstract boolean manuallyEndNavigationUponCompletion(); public abstract boolean enableNotification(); @@ -73,6 +75,8 @@ public abstract static class Builder { public abstract Builder enableOffRouteDetection(boolean enableOffRouteDetection); + public abstract Builder enableFasterRouteDetection(boolean enableFasterRouteDetection); + public abstract Builder manuallyEndNavigationUponCompletion(boolean manuallyEndNavigation); public abstract Builder enableNotification(boolean enableNotification); @@ -102,6 +106,7 @@ public static Builder builder() { .userLocationSnapDistance(NavigationConstants.USER_LOCATION_SNAPPING_DISTANCE) .secondsBeforeReroute(NavigationConstants.SECONDS_BEFORE_REROUTE) .enableOffRouteDetection(true) + .enableFasterRouteDetection(false) .snapToRoute(true) .manuallyEndNavigationUponCompletion(false) .defaultMilestonesEnabled(true) diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationConstants.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationConstants.java index 62ec4337fb8..2dafd644e25 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationConstants.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationConstants.java @@ -151,6 +151,23 @@ private NavigationConstants() { */ public static final String NAVIGATION_VIEW_DARK_THEME = "navigation_view_dark_theme"; + /** + * In seconds, how quickly {@link com.mapbox.services.android.navigation.v5.route.FasterRouteDetector} + * will tell {@link NavigationEngine} to check + * for a faster {@link com.mapbox.api.directions.v5.models.DirectionsRoute}. + * + * @since 0.9.0 + */ + public static final int NAVIGATION_CHECK_FASTER_ROUTE_INTERVAL = 120; + + /** + * 70 seconds remaining is considered a medium alert level when + * navigating along a {@link com.mapbox.api.directions.v5.models.LegStep}. + * + * @since 0.9.0 + */ + public static final int NAVIGATION_MEDIUM_ALERT_DURATION = 70; + // Bundle variable keys public static final String NAVIGATION_VIEW_ORIGIN_LAT_KEY = "origin_lat"; public static final String NAVIGATION_VIEW_ORIGIN_LNG_KEY = "origin_long"; diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEngine.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEngine.java index e1016270d21..15407cb227b 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEngine.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEngine.java @@ -16,6 +16,7 @@ import java.util.List; +import static com.mapbox.core.constants.Constants.PRECISION_6; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.bearingMatchesManeuverFinalHeading; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.checkMilestones; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.getSnappedLocation; @@ -23,9 +24,9 @@ import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.isUserOffRoute; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.legDistanceRemaining; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.routeDistanceRemaining; +import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.shouldCheckFasterRoute; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.stepDistanceRemaining; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.userSnappedToRoutePosition; -import static com.mapbox.core.constants.Constants.PRECISION_6; /** * This class extends handler thread to run most of the navigation calculations on a separate @@ -67,14 +68,24 @@ private void handleRequest(final NewLocationModel newLocationModel) { final RouteProgress routeProgress = generateNewRouteProgress( newLocationModel.mapboxNavigation(), newLocationModel.location(), newLocationModel.recentDistancesFromManeuverInMeters()); + + // Check milestone list to see if any should be triggered final List milestones = checkMilestones( previousRouteProgress, routeProgress, newLocationModel.mapboxNavigation()); + + // Check if user has gone off-route final boolean userOffRoute = isUserOffRoute(newLocationModel, routeProgress); + + // Create snapped location final Location location = !userOffRoute && newLocationModel.mapboxNavigation().options().snapToRoute() ? getSnappedLocation(newLocationModel.mapboxNavigation(), newLocationModel.location(), routeProgress, stepPositions) : newLocationModel.location(); + // Check for faster route only if enabled and not off-route + final boolean checkFasterRoute = newLocationModel.mapboxNavigation().options().enableFasterRouteDetection() + && !userOffRoute && shouldCheckFasterRoute(newLocationModel, routeProgress); + previousRouteProgress = routeProgress; responseHandler.post(new Runnable() { @@ -83,6 +94,7 @@ public void run() { callback.onNewRouteProgress(location, routeProgress); callback.onMilestoneTrigger(milestones, routeProgress); callback.onUserOffRoute(location, userOffRoute); + callback.onCheckFasterRoute(location, routeProgress, checkFasterRoute); } }); } @@ -161,5 +173,7 @@ interface Callback { void onMilestoneTrigger(List triggeredMilestones, RouteProgress routeProgress); void onUserOffRoute(Location location, boolean userOffRoute); + + void onCheckFasterRoute(Location location, RouteProgress routeProgress, boolean checkFasterRoute); } } diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcher.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcher.java index ac13cf83787..dbb2fe9196e 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcher.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcher.java @@ -4,10 +4,12 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.mapbox.api.directions.v5.models.DirectionsRoute; import com.mapbox.services.android.navigation.v5.milestone.Milestone; import com.mapbox.services.android.navigation.v5.milestone.MilestoneEventListener; import com.mapbox.services.android.navigation.v5.navigation.metrics.NavigationMetricListeners; import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener; +import com.mapbox.services.android.navigation.v5.route.FasterRouteListener; import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; import com.mapbox.services.android.navigation.v5.utils.RouteUtils; @@ -23,6 +25,7 @@ class NavigationEventDispatcher { private List milestoneEventListeners; private List progressChangeListeners; private List offRouteListeners; + private List fasterRouteListeners; private NavigationMetricListeners.EventListeners metricEventListeners; private NavigationMetricListeners.ArrivalListener metricArrivalListener; @@ -31,6 +34,7 @@ class NavigationEventDispatcher { milestoneEventListeners = new ArrayList<>(); progressChangeListeners = new ArrayList<>(); offRouteListeners = new ArrayList<>(); + fasterRouteListeners = new ArrayList<>(); } void addMilestoneEventListener(@NonNull MilestoneEventListener milestoneEventListener) { @@ -105,6 +109,24 @@ void removeNavigationEventListener(@Nullable NavigationEventListener navigationE } } + void addFasterRouteListener(@NonNull FasterRouteListener fasterRouteListener) { + if (fasterRouteListeners.contains(fasterRouteListener)) { + Timber.w("The specified FasterRouteListener has already been added to the stack."); + return; + } + fasterRouteListeners.add(fasterRouteListener); + } + + void removeFasterRouteListener(@Nullable FasterRouteListener fasterRouteListener) { + if (fasterRouteListener == null) { + fasterRouteListeners.clear(); + } else if (!fasterRouteListeners.contains(fasterRouteListener)) { + Timber.w("The specified FasterRouteListener isn't found in stack, therefore, cannot be removed."); + } else { + fasterRouteListeners.remove(fasterRouteListener); + } + } + void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) { for (MilestoneEventListener milestoneEventListener : milestoneEventListeners) { milestoneEventListener.onMilestoneEvent(routeProgress, instruction, milestone); @@ -152,6 +174,12 @@ void onNavigationEvent(boolean isRunning) { } } + void onFasterRouteEvent(DirectionsRoute directionsRoute) { + for (FasterRouteListener fasterRouteListener : fasterRouteListeners) { + fasterRouteListener.fasterRouteFound(directionsRoute); + } + } + void addMetricEventListeners(NavigationMetricListeners.EventListeners eventListeners) { this.metricEventListeners = eventListeners; } diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationHelper.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationHelper.java index 50dcc7f8380..53ccaab9f1d 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationHelper.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationHelper.java @@ -10,6 +10,7 @@ import com.mapbox.geojson.Point; import com.mapbox.services.android.navigation.v5.milestone.Milestone; import com.mapbox.services.android.navigation.v5.offroute.OffRoute; +import com.mapbox.services.android.navigation.v5.route.FasterRoute; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; import com.mapbox.services.android.navigation.v5.snap.Snap; import com.mapbox.services.android.telemetry.utils.MathUtils; @@ -185,6 +186,11 @@ static boolean isUserOffRoute(NewLocationModel newLocationModel, RouteProgress r newLocationModel.recentDistancesFromManeuverInMeters()); } + static boolean shouldCheckFasterRoute(NewLocationModel newLocationModel, RouteProgress routeProgress) { + FasterRoute fasterRoute = newLocationModel.mapboxNavigation().getFasterRouteEngine(); + return fasterRoute.shouldCheckFasterRoute(newLocationModel.location(), routeProgress); + } + static Location getSnappedLocation(MapboxNavigation mapboxNavigation, Location location, RouteProgress routeProgress, List stepCoordinates) { Snap snap = mapboxNavigation.getSnapEngine(); diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.java index ffe62ff4e57..7192e521099 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationRoute.java @@ -3,6 +3,7 @@ import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import com.mapbox.api.directions.v5.DirectionsCriteria; import com.mapbox.api.directions.v5.DirectionsCriteria.AnnotationCriteria; @@ -12,6 +13,7 @@ import com.mapbox.api.directions.v5.MapboxDirections; import com.mapbox.api.directions.v5.models.DirectionsResponse; import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.api.directions.v5.models.RouteOptions; import com.mapbox.core.exceptions.ServicesException; import com.mapbox.geojson.Point; @@ -420,6 +422,55 @@ public Builder baseUrl(String baseUrl) { return this; } + /** + * Optionally create a {@link Builder} based on all variables + * from given {@link RouteOptions}. + *

+ * Note: {@link RouteOptions#bearings()} are excluded because it's better + * to recalculate these at the time of the request, as your location bearing + * is constantly changing. + * + * @param options containing all variables for request + * @return this builder for chaining options together + * @since 0.9.0 + */ + public Builder routeOptions(RouteOptions options) { + + if (!TextUtils.isEmpty(options.language())) { + directionsBuilder.language(new Locale(options.language())); + } + + if (options.alternatives() != null) { + directionsBuilder.alternatives(options.alternatives()); + } + + if (!TextUtils.isEmpty(options.profile())) { + directionsBuilder.profile(options.profile()); + } + + if (options.alternatives() != null) { + directionsBuilder.alternatives(options.alternatives()); + } + + if (!TextUtils.isEmpty(options.voiceUnits())) { + directionsBuilder.voiceUnits(options.voiceUnits()); + } + + if (!TextUtils.isEmpty(options.user())) { + directionsBuilder.user(options.user()); + } + + if (!TextUtils.isEmpty(options.accessToken())) { + directionsBuilder.accessToken(options.accessToken()); + } + + if (!TextUtils.isEmpty(options.annotations())) { + directionsBuilder.annotations(options.annotations()); + } + + return this; + } + /** * This uses the provided parameters set using the {@link Builder} and adds the required * settings for navigation to work correctly. diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationService.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationService.java index 8034dc97376..7a9a6fada7f 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationService.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/NavigationService.java @@ -10,9 +10,12 @@ import android.os.IBinder; import android.support.annotation.Nullable; +import com.mapbox.api.directions.v5.models.DirectionsResponse; import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.geojson.Point; import com.mapbox.services.android.navigation.v5.milestone.Milestone; import com.mapbox.services.android.navigation.v5.navigation.notification.NavigationNotification; +import com.mapbox.services.android.navigation.v5.route.RouteEngine; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; import com.mapbox.services.android.navigation.v5.utils.RingBuffer; import com.mapbox.services.android.telemetry.location.LocationEngine; @@ -20,6 +23,7 @@ import java.util.List; +import retrofit2.Response; import timber.log.Timber; import static com.mapbox.services.android.navigation.v5.navigation.NavigationHelper.buildInstructionString; @@ -36,7 +40,7 @@ *

*/ public class NavigationService extends Service implements LocationEngineListener, - NavigationEngine.Callback { + NavigationEngine.Callback, RouteEngine.Callback { // Message id used when a new location update occurs and we send to the thread. private static final int MSG_LOCATION_UPDATED = 1001; @@ -46,6 +50,7 @@ public class NavigationService extends Service implements LocationEngineListener private NavigationNotification navigationNotification; private MapboxNavigation mapboxNavigation; + private RouteEngine routeEngine; private LocationEngine locationEngine; private NavigationEngine thread; @@ -136,6 +141,37 @@ public void onUserOffRoute(Location location, boolean userOffRoute) { } } + + /** + * Callback from the {@link NavigationEngine} - if fired with checkFasterRoute set + * to true, a new {@link DirectionsRoute} should be fetched with {@link RouteEngine}. + * + * @param location to create a new origin + * @param routeProgress for various {@link com.mapbox.api.directions.v5.models.LegStep} data + * @param checkFasterRoute true if should check for faster route, false otherwise + */ + @Override + public void onCheckFasterRoute(Location location, RouteProgress routeProgress, boolean checkFasterRoute) { + if (checkFasterRoute) { + Point origin = Point.fromLngLat(location.getLongitude(), location.getLatitude()); + routeEngine.fetchRoute(origin, routeProgress); + } + } + + /** + * Callback from the {@link RouteEngine} - if fired, a new and valid + * {@link DirectionsRoute} has been successfully retrieved. + * + * @param response with the new route + * @param routeProgress holding necessary leg / step information + */ + @Override + public void onResponseReceived(Response response, RouteProgress routeProgress) { + if (mapboxNavigation.getFasterRouteEngine().isFasterRoute(response.body(), routeProgress)) { + mapboxNavigation.getEventDispatcher().onFasterRouteEvent(response.body().routes().get(0)); + } + } + /** * This gets called when {@link MapboxNavigation#startNavigation(DirectionsRoute)} is called and * setups variables among other things on the Navigation Service side. @@ -143,6 +179,7 @@ public void onUserOffRoute(Location location, boolean userOffRoute) { void startNavigation(MapboxNavigation mapboxNavigation) { this.mapboxNavigation = mapboxNavigation; initNotification(mapboxNavigation); + initRouteEngine(mapboxNavigation); acquireLocationEngine(); forceLocationUpdate(); } @@ -203,6 +240,20 @@ private void initializeNotification(MapboxNavigationOptions options) { } } + /** + * Builds a new route engine which can be used to find faster routes + * during a navigation session based on traffic. + *

+ * Check to see if this functionality is enabled / disabled first. + * + * @param mapboxNavigation for options to check if enabled / disabled + */ + private void initRouteEngine(MapboxNavigation mapboxNavigation) { + if (mapboxNavigation.options().enableFasterRouteDetection()) { + routeEngine = new RouteEngine(this); + } + } + /** * Starts the given notification flagged as a foreground service. * diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRoute.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRoute.java new file mode 100644 index 00000000000..86b51cb2a83 --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRoute.java @@ -0,0 +1,52 @@ +package com.mapbox.services.android.navigation.v5.route; + +import android.location.Location; + +import com.mapbox.api.directions.v5.models.DirectionsResponse; +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; + +/** + * This class can be subclassed to provide custom logic for checking / determining + * new / faster routes while navigating. + *

+ * To provide your implementation, + * use {@link com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation#setFasterRouteEngine(FasterRoute)}. + *

+ * {@link FasterRoute#shouldCheckFasterRoute(Location, RouteProgress)} determines how quickly a + * new route will be fetched by {@link RouteEngine}. + *

+ * {@link FasterRoute#isFasterRoute(DirectionsResponse, RouteProgress)} determines if the new route + * retrieved by {@link RouteEngine} is actually faster than the current route. + * + * @since 0.9.0 + */ +public abstract class FasterRoute { + + /** + * This method determine if a new {@link DirectionsResponse} should + * be retrieved by {@link RouteEngine}. + *

+ * It will also be called every time + * the {@link com.mapbox.services.android.navigation.v5.navigation.NavigationEngine} gets a valid + * {@link Location} update. + *

+ * The most recent snapped location and route progress are provided. Both can be used to + * determine if a new route should be fetched or not. + * + * @param location current snapped location + * @param routeProgress current route progress + * @return true if should check, false if not + * @since 0.9.0 + */ + public abstract boolean shouldCheckFasterRoute(Location location, RouteProgress routeProgress); + + /** + * This method will be used to determine if the route retrieved is + * faster than the one that's currently being navigated. + * + * @param response provided by {@link RouteEngine} + * @param routeProgress current route progress + * @return true if the new route is considered faster, false if not + */ + public abstract boolean isFasterRoute(DirectionsResponse response, RouteProgress routeProgress); +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteDetector.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteDetector.java new file mode 100644 index 00000000000..c5226f3890b --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteDetector.java @@ -0,0 +1,133 @@ +package com.mapbox.services.android.navigation.v5.route; + +import android.location.Location; + +import com.mapbox.api.directions.v5.models.DirectionsResponse; +import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.api.directions.v5.models.LegStep; +import com.mapbox.api.directions.v5.models.RouteLeg; +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; +import com.mapbox.services.android.navigation.v5.routeprogress.RouteStepProgress; +import com.mapbox.services.android.navigation.v5.utils.time.TimeUtils; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.NAVIGATION_CHECK_FASTER_ROUTE_INTERVAL; +import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.NAVIGATION_MEDIUM_ALERT_DURATION; + +public class FasterRouteDetector extends FasterRoute { + + private static final int VALID_ROUTE_DURATION_REMAINING = 600; + + private Location lastCheckedLocation; + + @Override + public boolean shouldCheckFasterRoute(Location location, RouteProgress routeProgress) { + if (location == null || routeProgress == null) { + return false; + } + // On first pass through detector, last checked location will be null + if (lastCheckedLocation == null) { + lastCheckedLocation = location; + } + // Check if the faster route time interval has been exceeded + if (secondsSinceLastCheck(location) >= NAVIGATION_CHECK_FASTER_ROUTE_INTERVAL) { + lastCheckedLocation = location; + // Check for both valid route and step durations remaining + if (validRouteDurationRemaining(routeProgress) && validStepDurationRemaining(routeProgress)) { + return true; + } + } + return false; + } + + @Override + public boolean isFasterRoute(DirectionsResponse response, RouteProgress routeProgress) { + if (validRouteResponse(response)) { + + double currentDurationRemaining = routeProgress.durationRemaining(); + DirectionsRoute newRoute = response.routes().get(0); + + if (hasLegs(newRoute)) { + // Extract the first leg + RouteLeg routeLeg = newRoute.legs().get(0); + if (hasAtLeastTwoSteps(routeLeg)) { + // Extract the first two steps + LegStep firstStep = routeLeg.steps().get(0); + LegStep secondStep = routeLeg.steps().get(1); + // Check for valid first and second steps of the new route + if (!validFirstStep(firstStep) || !validSecondStep(secondStep, routeProgress)) { + return false; + } + } + } + // New route must be at least 10% faster + if (newRoute.duration() <= (0.9 * currentDurationRemaining)) { + return true; + } + } + return false; + } + + private boolean hasLegs(DirectionsRoute newRoute) { + return newRoute.legs() != null && !newRoute.legs().isEmpty(); + } + + private boolean hasAtLeastTwoSteps(RouteLeg routeLeg) { + return routeLeg.steps() != null && routeLeg.steps().size() > 2; + } + + /** + * The second step of the new route is valid if + * it equals the current route upcoming step. + * + * @param secondStep of the new route + * @param routeProgress current route progress + * @return true if valid, false if not + */ + private boolean validSecondStep(LegStep secondStep, RouteProgress routeProgress) { + return routeProgress.currentLegProgress().upComingStep() != null + && routeProgress.currentLegProgress().upComingStep().equals(secondStep); + } + + /** + * First step is valid if it is greater than + * {@link com.mapbox.services.android.navigation.v5.navigation.NavigationConstants#NAVIGATION_MEDIUM_ALERT_DURATION}. + * + * @param firstStep of the new route + * @return true if valid, false if not + */ + private boolean validFirstStep(LegStep firstStep) { + return firstStep.duration() > NAVIGATION_MEDIUM_ALERT_DURATION; + } + + /** + * Checks if we have at least one {@link DirectionsRoute} in the given + * {@link DirectionsResponse}. + * + * @param response to be checked + * @return true if valid, false if not + */ + private boolean validRouteResponse(DirectionsResponse response) { + return response != null + && !response.routes().isEmpty(); + } + + private boolean validRouteDurationRemaining(RouteProgress routeProgress) { + // Total route duration remaining in seconds + int routeDurationRemaining = (int) routeProgress.durationRemaining(); + return routeDurationRemaining > VALID_ROUTE_DURATION_REMAINING; + } + + private boolean validStepDurationRemaining(RouteProgress routeProgress) { + RouteStepProgress currentStepProgress = routeProgress.currentLegProgress().currentStepProgress(); + // Current step duration remaining in seconds + int currentStepDurationRemaining = (int) currentStepProgress.durationRemaining(); + return currentStepDurationRemaining > NAVIGATION_MEDIUM_ALERT_DURATION; + } + + private long secondsSinceLastCheck(Location location) { + return TimeUtils.dateDiff(new Date(lastCheckedLocation.getTime()), new Date(location.getTime()), TimeUnit.SECONDS); + } +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteListener.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteListener.java new file mode 100644 index 00000000000..d6e1d05f284 --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/FasterRouteListener.java @@ -0,0 +1,18 @@ +package com.mapbox.services.android.navigation.v5.route; + +import com.mapbox.api.directions.v5.models.DirectionsRoute; + +/** + * Listener that can be added to monitor faster routes retrieved + * based on the logic set in {@link FasterRoute}. + */ +public interface FasterRouteListener { + + /** + * Will be fired when a faster route has been found based on the logic + * provided by {@link FasterRoute}. + * + * @param directionsRoute faster route retrieved + */ + void fasterRouteFound(DirectionsRoute directionsRoute); +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/RouteEngine.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/RouteEngine.java new file mode 100644 index 00000000000..512e322597c --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/route/RouteEngine.java @@ -0,0 +1,86 @@ +package com.mapbox.services.android.navigation.v5.route; + +import android.support.annotation.NonNull; + +import com.mapbox.api.directions.v5.models.DirectionsResponse; +import com.mapbox.api.directions.v5.models.RouteOptions; +import com.mapbox.geojson.Point; +import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute; +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * This class can be used to fetch new routes given a {@link Point} origin and + * {@link RouteOptions} provided by a {@link RouteProgress}. + */ +public class RouteEngine implements Callback { + + private Callback engineCallback; + private RouteProgress routeProgress; + + public RouteEngine(Callback engineCallback) { + this.engineCallback = engineCallback; + } + + public void fetchRoute(Point origin, RouteProgress routeProgress) { + if (routeProgress == null) { + return; + } + this.routeProgress = routeProgress; + + // Calculate remaining waypoints + List coordinates = new ArrayList<>(routeProgress.directionsRoute().routeOptions().coordinates()); + + if (coordinates.size() < routeProgress.remainingWaypoints()) { + return; + } + // Remove any waypoints that have been passed + coordinates.subList(0, routeProgress.remainingWaypoints()).clear(); + // Get the destination waypoint (last in the list) + Point destination = coordinates.remove(coordinates.size() - 1); + + // Build new route request with the given origin and current route options + RouteOptions currentOptions = routeProgress.directionsRoute().routeOptions(); + NavigationRoute.Builder builder = NavigationRoute.builder() + .origin(origin) + .routeOptions(currentOptions); + + // Add waypoints with the remaining coordinate values + addWaypoints(coordinates, builder); + + builder.destination(destination); + builder.build().getRoute(this); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + // Check for successful response + if (!response.isSuccessful()) { + return; + } + engineCallback.onResponseReceived(response, routeProgress); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + // No-op - fail silently + } + + public interface Callback { + void onResponseReceived(Response response, RouteProgress routeProgress); + } + + private void addWaypoints(List remainingCoordinates, NavigationRoute.Builder builder) { + if (!remainingCoordinates.isEmpty()) { + for (Point coordinate : remainingCoordinates) { + builder.addWaypoint(coordinate); + } + } + } +} diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/FasterRouteDetectorTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/FasterRouteDetectorTest.java new file mode 100644 index 00000000000..ad119a0d3cc --- /dev/null +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/FasterRouteDetectorTest.java @@ -0,0 +1,147 @@ +package com.mapbox.services.android.navigation.v5.navigation; + +import android.content.Context; +import android.location.Location; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mapbox.api.directions.v5.DirectionsAdapterFactory; +import com.mapbox.api.directions.v5.models.DirectionsResponse; +import com.mapbox.api.directions.v5.models.DirectionsRoute; +import com.mapbox.services.android.navigation.v5.BaseTest; +import com.mapbox.services.android.navigation.v5.route.FasterRoute; +import com.mapbox.services.android.navigation.v5.route.FasterRouteDetector; +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; +import com.mapbox.services.android.telemetry.location.LocationEngine; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class FasterRouteDetectorTest extends BaseTest { + + private static final String PRECISION_6 = "directions_v5_precision_6.json"; + + private MapboxNavigation navigation; + + @Before + public void setup() throws IOException { + MapboxNavigationOptions options = MapboxNavigationOptions.builder() + .enableFasterRouteDetection(true) + .build(); + navigation = new MapboxNavigation(mock(Context.class), ACCESS_TOKEN, options, mock(NavigationTelemetry.class), + mock(LocationEngine.class)); + } + + @Test + public void sanity() throws Exception { + FasterRouteDetector fasterRouteDetector = new FasterRouteDetector(); + assertNotNull(fasterRouteDetector); + } + + @Test + public void defaultFasterRouteEngine_didGetAddedOnInitialization() throws Exception { + assertNotNull(navigation.getFasterRouteEngine()); + } + + @Test + public void addFasterRouteEngine_didGetAdded() throws Exception { + FasterRoute fasterRouteEngine = mock(FasterRoute.class); + navigation.setFasterRouteEngine(fasterRouteEngine); + assertEquals(navigation.getFasterRouteEngine(), fasterRouteEngine); + } + + @Test + public void onFasterRouteResponse_isFasterRouteIsTrue() throws Exception { + FasterRoute fasterRouteEngine = navigation.getFasterRouteEngine(); + + // Create current progress + RouteProgress currentProgress = obtainDefaultRouteProgress(); + DirectionsRoute longerRoute = currentProgress.directionsRoute().toBuilder() + .duration(10000000d) // Current route duration is very long + .build(); + currentProgress = currentProgress.toBuilder() + .directionsRoute(longerRoute) + .build(); + + // Create new direction response + DirectionsResponse response = obtainADirectionsResponse(); + + boolean isFasterRoute = fasterRouteEngine.isFasterRoute(response, currentProgress); + assertTrue(isFasterRoute); + } + + @Test + public void onSlowerRouteResponse_isFasterRouteIsFalse() throws Exception { + FasterRoute fasterRouteEngine = navigation.getFasterRouteEngine(); + + // Create current progress + RouteProgress currentProgress = obtainDefaultRouteProgress(); + DirectionsRoute longerRoute = currentProgress.directionsRoute().toBuilder() + .duration(1000d) // Current route duration is very short + .build(); + currentProgress = currentProgress.toBuilder() + .directionsRoute(longerRoute) + .build(); + + // Create new direction response + DirectionsResponse response = obtainADirectionsResponse(); + + boolean isFasterRoute = fasterRouteEngine.isFasterRoute(response, currentProgress); + assertFalse(isFasterRoute); + } + + @Test + public void onNullLocationPassed_shouldCheckFasterRouteIsFalse() throws Exception { + FasterRoute fasterRouteEngine = navigation.getFasterRouteEngine(); + + boolean checkFasterRoute = fasterRouteEngine.shouldCheckFasterRoute(null, obtainDefaultRouteProgress()); + assertFalse(checkFasterRoute); + } + + @Test + public void onNullRouteProgressPassed_shouldCheckFasterRouteIsFalse() throws Exception { + FasterRoute fasterRouteEngine = navigation.getFasterRouteEngine(); + + boolean checkFasterRoute = fasterRouteEngine.shouldCheckFasterRoute(mock(Location.class), null); + assertFalse(checkFasterRoute); + } + + private RouteProgress obtainDefaultRouteProgress() throws Exception { + DirectionsRoute aRoute = obtainADirectionsRoute(); + RouteProgress defaultRouteProgress = RouteProgress.builder() + .stepDistanceRemaining(100) + .legDistanceRemaining(700) + .distanceRemaining(1000) + .directionsRoute(aRoute) + .stepIndex(0) + .legIndex(0) + .build(); + + return defaultRouteProgress; + } + + private DirectionsRoute obtainADirectionsRoute() throws IOException { + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(DirectionsAdapterFactory.create()).create(); + String body = loadJsonFixture(PRECISION_6); + DirectionsResponse response = gson.fromJson(body, DirectionsResponse.class); + DirectionsRoute aRoute = response.routes().get(0); + return aRoute; + } + + private DirectionsResponse obtainADirectionsResponse() throws IOException { + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(DirectionsAdapterFactory.create()).create(); + String body = loadJsonFixture(PRECISION_6); + DirectionsResponse response = gson.fromJson(body, DirectionsResponse.class); + return response; + } +} diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcherTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcherTest.java index b6c24969592..5deeee24287 100644 --- a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcherTest.java +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/NavigationEventDispatcherTest.java @@ -15,6 +15,7 @@ import com.mapbox.services.android.navigation.v5.milestone.MilestoneEventListener; import com.mapbox.services.android.navigation.v5.navigation.metrics.NavigationMetricListeners; import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener; +import com.mapbox.services.android.navigation.v5.route.FasterRouteListener; import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; import com.mapbox.services.android.telemetry.location.LocationEngine; @@ -53,6 +54,8 @@ public class NavigationEventDispatcherTest extends BaseTest { @Mock NavigationEventListener navigationEventListener; @Mock + FasterRouteListener fasterRouteListener; + @Mock Location location; @Mock Milestone milestone; @@ -260,6 +263,50 @@ public void removeNavigationEventListener_nullRemovesAllListeners() throws Excep verify(navigationEventListener, times(0)).onRunning(true); } + @Test + public void addFasterRouteListener_didAddListener() throws Exception { + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(0)).fasterRouteFound(route); + + navigation.addFasterRouteListener(fasterRouteListener); + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(1)).fasterRouteFound(route); + } + + @Test + public void addFasterRouteListener_onlyAddsListenerOnce() throws Exception { + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(0)).fasterRouteFound(route); + + navigation.addFasterRouteListener(fasterRouteListener); + navigation.addFasterRouteListener(fasterRouteListener); + navigation.addFasterRouteListener(fasterRouteListener); + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(1)).fasterRouteFound(route); + } + + @Test + public void removeFasterRouteListener_didRemoveListener() throws Exception { + navigation.addFasterRouteListener(fasterRouteListener); + navigation.removeFasterRouteListener(fasterRouteListener); + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(0)).fasterRouteFound(route); + } + + @Test + public void removeFasterRouteListener_nullRemovesAllListeners() throws Exception { + navigation.addFasterRouteListener(fasterRouteListener); + navigation.addFasterRouteListener(mock(FasterRouteListener.class)); + navigation.addFasterRouteListener(mock(FasterRouteListener.class)); + navigation.addFasterRouteListener(mock(FasterRouteListener.class)); + navigation.addFasterRouteListener(mock(FasterRouteListener.class)); + + navigation.removeFasterRouteListener(null); + navigationEventDispatcher.onFasterRouteEvent(route); + verify(fasterRouteListener, times(0)).fasterRouteFound(route); + } + + @Test public void setNavigationMetricListener_didGetSet() throws Exception { navigationEventDispatcher.addMetricEventListeners(eventListeners); diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/utils/MeasurementUtilsTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/utils/MeasurementUtilsTest.java index a7af723bdc0..d68f84dbbc3 100644 --- a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/utils/MeasurementUtilsTest.java +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/utils/MeasurementUtilsTest.java @@ -22,7 +22,8 @@ public void userTrueDistanceFromStep_returnsZeroWhenCurrentStepAndPointEqualSame List geometryPoints = new ArrayList<>(); geometryPoints.add(futurePoint); - LegStep step = createTestStep(geometryPoints); + double[] rawLocation = {0, 0}; + LegStep step = getLegStep(rawLocation, geometryPoints); double distance = MeasurementUtils.userTrueDistanceFromStep(futurePoint, step); assertEquals(0d, distance, DELTA); @@ -34,7 +35,8 @@ public void userTrueDistanceFromStep_onlyOnePointInLineStringStillMeasuresDistan List geometryPoints = new ArrayList<>(); geometryPoints.add(Point.fromLngLat(-95.8427, 29.7757)); - LegStep step = createTestStep(geometryPoints); + double[] rawLocation = {0, 0}; + LegStep step = getLegStep(rawLocation, geometryPoints); double distance = MeasurementUtils.userTrueDistanceFromStep(futurePoint, step); assertEquals(45900.73617999494, distance, DELTA); @@ -47,25 +49,21 @@ public void userTrueDistanceFromStep_onePointStepGeometryWithDifferentRawPoint() List geometryPoints = new ArrayList<>(); geometryPoints.add(Point.fromLngLat(-95.8427, 29.7757)); geometryPoints.add(futurePoint); - LegStep step = createTestStep(geometryPoints); + double[] rawLocation = {0, 0}; + LegStep step = getLegStep(rawLocation, geometryPoints); double distance = MeasurementUtils.userTrueDistanceFromStep(futurePoint, step); assertEquals(0.04457271773629306d, distance, DELTA); } - private LegStep createTestStep(List geometryPoints) { - double[] location = {0d, 0d}; - StepManeuver maneuver = StepManeuver.builder() - .rawLocation(location) - .build(); - + private LegStep getLegStep(double[] rawLocation, List geometryPoints) { return LegStep.builder() .geometry(PolylineUtils.encode(geometryPoints, PRECISION_6)) .mode("driving") - .distance(2000d) - .duration(1000d) - .maneuver(maneuver) - .weight(0d) + .distance(0) + .duration(0) + .maneuver(StepManeuver.builder().rawLocation(rawLocation).build()) + .weight(0) .build(); } } \ No newline at end of file