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 b71ac2aba83..ba4c43306ac 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 @@ -67,6 +67,7 @@ public class MapboxNavigation implements ServiceConnection { private final String accessToken; private Context applicationContext; private boolean isBound; + private RouteRefresher routeRefresher; static { NavigationLibraryLoader.load(); @@ -845,6 +846,11 @@ LocationEngineRequest retrieveLocationEngineRequest() { return locationEngineRequest; } + @Nullable + RouteRefresher retrieveRouteRefresher() { + return routeRefresher; + } + private void initializeForTest() { // Initialize event dispatcher and add internal listeners navigationEventDispatcher = new NavigationEventDispatcher(); @@ -926,6 +932,7 @@ private LocationEngineRequest obtainLocationEngineRequest() { private void startNavigationWith(@NonNull DirectionsRoute directionsRoute, DirectionsRouteType routeType) { ValidationUtils.validDirectionsRoute(directionsRoute, options.defaultMilestonesEnabled()); this.directionsRoute = directionsRoute; + routeRefresher = new RouteRefresher(this, new RouteRefresh(accessToken), 5 * 60 * 1000); mapboxNavigator.updateRoute(directionsRoute, routeType); if (!isBound) { navigationTelemetry.startSession(directionsRoute, locationEngine); 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 1e91bcd6594..76bc309992b 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 @@ -680,7 +680,8 @@ public NavigationRoute build() { .voiceInstructions(true) .bannerInstructions(true) .roundaboutExits(true) - .eventListener(eventListener); + .eventListener(eventListener) + .enableRefresh(true); return new NavigationRoute(directionsBuilder.build()); } diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteProcessorRunnable.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteProcessorRunnable.java index 2a14ff0caeb..4a77bbfeaa3 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteProcessorRunnable.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteProcessorRunnable.java @@ -55,11 +55,17 @@ private void process() { MapboxNavigationOptions options = navigation.options(); DirectionsRoute route = navigation.getRoute(); - NavigationStatus status = mapboxNavigator.retrieveStatus(new Date(), + Date date = new Date(); + NavigationStatus status = mapboxNavigator.retrieveStatus(date, options.navigationLocationEngineIntervalLagInMilliseconds()); status = checkForNewLegIndex(mapboxNavigator, route, status, options.enableAutoIncrementLegIndex()); RouteProgress routeProgress = routeProcessor.buildNewRouteProgress(mapboxNavigator, status, route); + RouteRefresher routeRefresher = navigation.retrieveRouteRefresher(); + if (routeRefresher != null && routeRefresher.check(date)) { + routeRefresher.refresh(routeProgress); + } + NavigationEngineFactory engineFactory = navigation.retrieveEngineFactory(); final boolean userOffRoute = isUserOffRoute(options, status, rawLocation, routeProgress, engineFactory); final Location snappedLocation = findSnappedLocation(status, rawLocation, routeProgress, engineFactory); diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresh.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresh.java index f28f25d73ad..09541b8d76b 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresh.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresh.java @@ -4,40 +4,77 @@ import com.mapbox.api.directionsrefresh.v1.MapboxDirectionsRefresh; import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; +import timber.log.Timber; + /** * This class allows the developer to interact with the Directions Refresh API, receiving updated * annotations for a route previously requested with the enableRefresh flag. */ public final class RouteRefresh { - private final RefreshCallback refreshCallback; + private RefreshCallback refreshCallback; private final String accessToken; /** * Creates a {@link RouteRefresh} object which calls the {@link RefreshCallback} with updated * routes. * - * @param accessToken mapbox access token + * @param accessToken mapbox access token * @param refreshCallback to call with updated routes + * @deprecated use {@link RouteRefresh(String)} instead */ + @Deprecated public RouteRefresh(String accessToken, RefreshCallback refreshCallback) { this.accessToken = accessToken; this.refreshCallback = refreshCallback; } + /** + * Creates a {@link RouteRefresh} object. + * + * @param accessToken mapbox access token + */ + public RouteRefresh(String accessToken) { + this.accessToken = accessToken; + } + /** * Refreshes the {@link DirectionsRoute} included in the {@link RouteProgress} and returns it * to the callback that was originally passed in. The client will then have to update their * {@link DirectionsRoute} with the {@link com.mapbox.api.directions.v5.models.LegAnnotation}s * returned in this response. The leg annotations start at the current leg index of the * {@link RouteProgress} + *

+ * Note that if {@link RefreshCallback} is not passed in {@link RouteRefresh(String, RefreshCallback)} this call + * will be ignored. + *

* * @param routeProgress to refresh via the route and current leg index + * @deprecated use {@link RouteRefresh#refresh(RouteProgress, RefreshCallback)} instead */ + @Deprecated public void refresh(RouteProgress routeProgress) { - refresh(routeProgress.directionsRoute(), routeProgress.legIndex()); + if (refreshCallback == null) { + Timber.e("RefreshCallback cannot be null."); + return; + } + refresh(routeProgress.directionsRoute(), routeProgress.legIndex(), refreshCallback); + } + + /** + * Refreshes the {@link DirectionsRoute} included in the {@link RouteProgress} and returns it + * to the callback that was originally passed in. The client will then have to update their + * {@link DirectionsRoute} with the {@link com.mapbox.api.directions.v5.models.LegAnnotation}s + * returned in this response. The leg annotations start at the current leg index of the + * {@link RouteProgress} + * + * @param routeProgress to refresh via the route and current leg index + * @param refreshCallback to call with updated routes + */ + public void refresh(RouteProgress routeProgress, RefreshCallback refreshCallback) { + refresh(routeProgress.directionsRoute(), routeProgress.legIndex(), refreshCallback); } - private void refresh(final DirectionsRoute directionsRoute, final int legIndex) { + private void refresh(final DirectionsRoute directionsRoute, final int legIndex, RefreshCallback refreshCallback) { MapboxDirectionsRefresh.builder() .requestId(directionsRoute.routeOptions().requestUuid()) .routeIndex(Integer.valueOf(directionsRoute.routeIndex())) diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefreshCallback.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefreshCallback.java index a95d3ee13bb..b6c6e2d6c8c 100644 --- a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefreshCallback.java +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefreshCallback.java @@ -13,8 +13,7 @@ class RouteRefreshCallback implements Callback { private final int legIndex; private final RefreshCallback refreshCallback; - RouteRefreshCallback(DirectionsRoute directionsRoute, - int legIndex, RefreshCallback refreshCallback) { + RouteRefreshCallback(DirectionsRoute directionsRoute, int legIndex, RefreshCallback refreshCallback) { this(new RouteAnnotationUpdater(), directionsRoute, legIndex, refreshCallback); } @@ -31,13 +30,12 @@ public void onResponse(Call call, Response call, Throwable throwable) { - refreshCallback.onError(new RefreshError("There was a network error.")); + refreshCallback.onError(new RefreshError(throwable.getMessage())); } } diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresher.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresher.java new file mode 100644 index 00000000000..2df55888d8e --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresher.java @@ -0,0 +1,41 @@ +package com.mapbox.services.android.navigation.v5.navigation; + +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; + +import java.util.Date; + +class RouteRefresher { + private final RouteRefresh routeRefresh; + private final MapboxNavigation mapboxNavigation; + private final long refreshIntervalInMilliseconds; + private Date lastRefreshedDate; + private boolean isChecking; + + RouteRefresher(MapboxNavigation mapboxNavigation, RouteRefresh routeRefresh, long refreshIntervalInMilliseconds) { + this.mapboxNavigation = mapboxNavigation; + this.routeRefresh = routeRefresh; + this.refreshIntervalInMilliseconds = refreshIntervalInMilliseconds; + this.lastRefreshedDate = new Date(); + } + + boolean check(Date currentDate) { + if (isChecking) { + return false; + } + long millisSinceLastRefresh = currentDate.getTime() - lastRefreshedDate.getTime(); + return millisSinceLastRefresh > refreshIntervalInMilliseconds; + } + + void refresh(RouteProgress routeProgress) { + updateIsChecking(true); + routeRefresh.refresh(routeProgress, new RouteRefresherCallback(mapboxNavigation, this)); + } + + void updateLastRefresh(Date date) { + lastRefreshedDate = date; + } + + void updateIsChecking(boolean isChecking) { + this.isChecking = isChecking; + } +} diff --git a/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallback.java b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallback.java new file mode 100644 index 00000000000..713cccd1753 --- /dev/null +++ b/libandroid-navigation/src/main/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallback.java @@ -0,0 +1,30 @@ +package com.mapbox.services.android.navigation.v5.navigation; + +import com.mapbox.api.directions.v5.models.DirectionsRoute; + +import java.util.Date; + +import timber.log.Timber; + +class RouteRefresherCallback implements RefreshCallback { + private final MapboxNavigation mapboxNavigation; + private final RouteRefresher routeRefresher; + + RouteRefresherCallback(MapboxNavigation mapboxNavigation, RouteRefresher routeRefresher) { + this.mapboxNavigation = mapboxNavigation; + this.routeRefresher = routeRefresher; + } + + @Override + public void onRefresh(DirectionsRoute directionsRoute) { + mapboxNavigation.startNavigation(directionsRoute, DirectionsRouteType.FRESH_ROUTE); + routeRefresher.updateLastRefresh(new Date()); + routeRefresher.updateIsChecking(false); + } + + @Override + public void onError(RefreshError error) { + Timber.w(error.getMessage()); + routeRefresher.updateIsChecking(false); + } +} diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallbackTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallbackTest.java new file mode 100644 index 00000000000..114477c0039 --- /dev/null +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherCallbackTest.java @@ -0,0 +1,67 @@ +package com.mapbox.services.android.navigation.v5.navigation; + +import com.mapbox.api.directions.v5.models.DirectionsRoute; + +import org.junit.Test; + +import java.util.Date; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class RouteRefresherCallbackTest { + + @Test + public void checksStartNavigationWithRefreshedRouteIsCalledWhenOnRefresh() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresher mockedRouteRefresher = mock(RouteRefresher.class); + RouteRefresherCallback theRouteRefresherCallback = new RouteRefresherCallback(mockedMapboxNavigation, + mockedRouteRefresher); + DirectionsRoute anyRoute = mock(DirectionsRoute.class); + + theRouteRefresherCallback.onRefresh(anyRoute); + + verify(mockedMapboxNavigation).startNavigation(eq(anyRoute), eq(DirectionsRouteType.FRESH_ROUTE)); + } + + @Test + public void checksUpdateLastRefreshDateIsCalledWhenOnRefresh() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresher mockedRouteRefresher = mock(RouteRefresher.class); + RouteRefresherCallback theRouteRefresherCallback = new RouteRefresherCallback(mockedMapboxNavigation, + mockedRouteRefresher); + DirectionsRoute anyRoute = mock(DirectionsRoute.class); + + theRouteRefresherCallback.onRefresh(anyRoute); + + verify(mockedRouteRefresher).updateLastRefresh(any(Date.class)); + } + + @Test + public void checksUpdateIsNotCheckingAfterOnRefresh() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresher mockedRouteRefresher = mock(RouteRefresher.class); + RouteRefresherCallback theRouteRefresherCallback = new RouteRefresherCallback(mockedMapboxNavigation, + mockedRouteRefresher); + DirectionsRoute anyRoute = mock(DirectionsRoute.class); + + theRouteRefresherCallback.onRefresh(anyRoute); + + verify(mockedRouteRefresher).updateIsChecking(eq(false)); + } + + @Test + public void checksUpdateIsNotCheckingIfOnError() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresher mockedRouteRefresher = mock(RouteRefresher.class); + RouteRefresherCallback theRouteRefresherCallback = new RouteRefresherCallback(mockedMapboxNavigation, + mockedRouteRefresher); + RefreshError anyRefreshError = mock(RefreshError.class); + + theRouteRefresherCallback.onError(anyRefreshError); + + verify(mockedRouteRefresher).updateIsChecking(eq(false)); + } +} \ No newline at end of file diff --git a/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherTest.java b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherTest.java new file mode 100644 index 00000000000..8f64b206c15 --- /dev/null +++ b/libandroid-navigation/src/test/java/com/mapbox/services/android/navigation/v5/navigation/RouteRefresherTest.java @@ -0,0 +1,94 @@ +package com.mapbox.services.android.navigation.v5.navigation; + +import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress; + +import org.junit.Test; + +import java.util.Date; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class RouteRefresherTest { + + @Test + public void checksShouldRefreshIfIntervalHasPassed() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresh mockedRouteRefresh = mock(RouteRefresh.class); + long aRefreshIntervalInMilliseconds = 1; + RouteRefresher theRouteRefresher = new RouteRefresher(mockedMapboxNavigation, mockedRouteRefresh, + aRefreshIntervalInMilliseconds); + Date aDate = new Date(); + Date aDatePassedTheInterval = new Date(aDate.getTime() + aRefreshIntervalInMilliseconds + 1); + + boolean shouldRefresh = theRouteRefresher.check(aDatePassedTheInterval); + + assertTrue(shouldRefresh); + } + + @Test + public void checksShouldNotRefreshIfIntervalHasNotPassed() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresh mockedRouteRefresh = mock(RouteRefresh.class); + long aRefreshIntervalInMilliseconds = 1; + RouteRefresher theRouteRefresher = new RouteRefresher(mockedMapboxNavigation, mockedRouteRefresh, + aRefreshIntervalInMilliseconds); + Date aDate = new Date(); + Date aDatePassedTheInterval = new Date(aDate.getTime() + aRefreshIntervalInMilliseconds); + + boolean shouldRefresh = theRouteRefresher.check(aDatePassedTheInterval); + + assertFalse(shouldRefresh); + } + + @Test + public void checksShouldNotRefreshIfCurrentlyChecking() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresh mockedRouteRefresh = mock(RouteRefresh.class); + long aRefreshIntervalInMilliseconds = 1; + RouteRefresher theRouteRefresher = new RouteRefresher(mockedMapboxNavigation, mockedRouteRefresh, + aRefreshIntervalInMilliseconds); + Date aDate = new Date(); + Date aDatePassedTheInterval = new Date(aDate.getTime() + aRefreshIntervalInMilliseconds + 1); + + theRouteRefresher.updateIsChecking(true); + boolean shouldRefresh = theRouteRefresher.check(aDatePassedTheInterval); + + assertFalse(shouldRefresh); + } + + @Test + public void checksUpdateIsCheckingWhenRefresh() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresh mockedRouteRefresh = mock(RouteRefresh.class); + long aRefreshIntervalInMilliseconds = 1; + RouteRefresher theRouteRefresher = new RouteRefresher(mockedMapboxNavigation, mockedRouteRefresh, + aRefreshIntervalInMilliseconds); + Date aDate = new Date(); + Date aDatePassedTheInterval = new Date(aDate.getTime() + aRefreshIntervalInMilliseconds + 2); + RouteProgress mockedRouteProgress = mock(RouteProgress.class); + + theRouteRefresher.refresh(mockedRouteProgress); + boolean shouldRefresh = theRouteRefresher.check(aDatePassedTheInterval); + + assertFalse(shouldRefresh); + } + + @Test + public void checksRouteRefreshIsCalledWhenRefresh() { + MapboxNavigation mockedMapboxNavigation = mock(MapboxNavigation.class); + RouteRefresh mockedRouteRefresh = mock(RouteRefresh.class); + long aRefreshIntervalInMilliseconds = 1; + RouteRefresher theRouteRefresher = new RouteRefresher(mockedMapboxNavigation, mockedRouteRefresh, + aRefreshIntervalInMilliseconds); + RouteProgress mockedRouteProgress = mock(RouteProgress.class); + + theRouteRefresher.refresh(mockedRouteProgress); + + verify(mockedRouteRefresh).refresh(eq(mockedRouteProgress), any(RouteRefresherCallback.class)); + } +} \ No newline at end of file