Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add periodic refresh #1855

Merged
merged 1 commit into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
import timber.log.Timber;

import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.BANNER_INSTRUCTION_MILESTONE_ID;
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.NON_NULL_APPLICATION_CONTEXT_REQUIRED;
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants
.NON_NULL_APPLICATION_CONTEXT_REQUIRED;
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.VOICE_INSTRUCTION_MILESTONE_ID;

/**
Expand Down Expand Up @@ -67,6 +68,7 @@ public class MapboxNavigation implements ServiceConnection {
private final String accessToken;
private Context applicationContext;
private boolean isBound;
private RouteRefresher routeRefresher;

static {
NavigationLibraryLoader.load();
Expand Down Expand Up @@ -845,6 +847,11 @@ LocationEngineRequest retrieveLocationEngineRequest() {
return locationEngineRequest;
}

@Nullable
RouteRefresher retrieveRouteRefresher() {
return routeRefresher;
}

private void initializeForTest() {
// Initialize event dispatcher and add internal listeners
navigationEventDispatcher = new NavigationEventDispatcher();
Expand Down Expand Up @@ -926,6 +933,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));
mapboxNavigator.updateRoute(directionsRoute, routeType);
if (!isBound) {
navigationTelemetry.startSession(directionsRoute, locationEngine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants
.NAVIGATION_LOCATION_ENGINE_INTERVAL_LAG;
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.ROUNDING_INCREMENT_FIFTY;
import static com.mapbox.services.android.navigation.v5.navigation.NavigationConstants.ROUTE_REFRESH_INTERVAL;

/**
* Immutable and can't be changed after passing into {@link MapboxNavigation}.
Expand All @@ -21,6 +22,20 @@ public abstract class MapboxNavigationOptions {

public abstract boolean enableAutoIncrementLegIndex();

/**
* This value indicates if route refresh is enabled or disabled.
*
* @return whether route refresh is enabled or not
*/
public abstract boolean enableRefreshRoute();

/**
* This value indicates the route refresh interval.
*
* @return route refresh interval in milliseconds
*/
public abstract long refreshIntervalInMilliseconds();

public abstract boolean isFromNavigationUi();

public abstract boolean isDebugLoggingEnabled();
Expand All @@ -47,6 +62,22 @@ public abstract static class Builder {

public abstract Builder enableAutoIncrementLegIndex(boolean enableAutoIncrementLegIndex);

/**
* This enables / disables refresh route. If not specified, it's enabled by default.
*
* @param enableRefreshRoute whether or not to enable route refresh
* @return this builder for chaining options together
*/
public abstract Builder enableRefreshRoute(boolean enableRefreshRoute);

/**
* This sets the route refresh interval. If not specified, the interval is 5 minutes by default.
*
* @param intervalInMilliseconds for route refresh
* @return this builder for chaining options together
*/
public abstract Builder refreshIntervalInMilliseconds(long intervalInMilliseconds);

public abstract Builder isFromNavigationUi(boolean isFromNavigationUi);

public abstract Builder isDebugLoggingEnabled(boolean debugLoggingEnabled);
Expand All @@ -66,6 +97,8 @@ public static Builder builder() {
return new AutoValue_MapboxNavigationOptions.Builder()
.enableFasterRouteDetection(false)
.enableAutoIncrementLegIndex(true)
.enableRefreshRoute(true)
.refreshIntervalInMilliseconds(ROUTE_REFRESH_INTERVAL)
.defaultMilestonesEnabled(true)
.isFromNavigationUi(false)
.isDebugLoggingEnabled(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ private NavigationConstants() {
*/
static final int NAVIGATION_LOCATION_ENGINE_INTERVAL_LAG = 1500;

static final long ROUTE_REFRESH_INTERVAL = 5 * 60 * 1000L;

/**
* Defines the minimum zoom level of the displayed map.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,8 @@ public NavigationRoute build() {
.voiceInstructions(true)
.bannerInstructions(true)
.roundaboutExits(true)
.eventListener(eventListener);
.eventListener(eventListener)
.enableRefresh(true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling refresh by default

return new NavigationRoute(directionsBuilder.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
* <p>
* Note that if {@link RefreshCallback} is not passed in {@link RouteRefresh(String, RefreshCallback)} this call
* will be ignored.
* </p>
*
* @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()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class RouteRefreshCallback implements Callback<DirectionsRefreshResponse> {
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);
}

Expand All @@ -31,13 +30,12 @@ public void onResponse(Call<DirectionsRefreshResponse> call, Response<Directions
if (response.body() == null || response.body().route() == null) {
refreshCallback.onError(new RefreshError(response.message()));
} else {
refreshCallback.onRefresh(routeAnnotationUpdater.update(directionsRoute,
response.body().route(), legIndex));
refreshCallback.onRefresh(routeAnnotationUpdater.update(directionsRoute, response.body().route(), legIndex));
}
}

@Override
public void onFailure(Call<DirectionsRefreshResponse> call, Throwable throwable) {
refreshCallback.onError(new RefreshError("There was a network error."));
refreshCallback.onError(new RefreshError(throwable.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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 MapboxNavigation mapboxNavigation;
private final RouteRefresh routeRefresh;
private final long refreshIntervalInMilliseconds;
private Date lastRefreshedDate;
private boolean isChecking;
private boolean isRefreshRouteEnabled;

RouteRefresher(MapboxNavigation mapboxNavigation, RouteRefresh routeRefresh) {
this.mapboxNavigation = mapboxNavigation;
this.routeRefresh = routeRefresh;
this.refreshIntervalInMilliseconds = mapboxNavigation.options().refreshIntervalInMilliseconds();
this.lastRefreshedDate = new Date();
this.isRefreshRouteEnabled = mapboxNavigation.options().enableRefreshRoute();
}

boolean check(Date currentDate) {
if (isChecking || !isRefreshRouteEnabled) {
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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading