Skip to content

Commit

Permalink
Add route refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle Madsen committed Mar 23, 2020
1 parent 3104a01 commit 7c87897
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.mapbox.annotation.navigation.module.MapboxNavigationModule
import com.mapbox.annotation.navigation.module.MapboxNavigationModuleType
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.Router
import com.mapbox.navigation.utils.network.NetworkStatusService
import com.mapbox.navigation.utils.thread.ThreadController
Expand Down Expand Up @@ -57,7 +58,8 @@ class MapboxHybridRouter(
* Private interface used with handler classes here to call the correct router
*/
private interface RouterDispatchInterface {
fun execute(routeOptions: RouteOptions, clientCallback: Router.Callback)
fun getRoute(routeOptions: RouteOptions, clientCallback: Router.Callback)
fun getRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback)
}

private class RouterHandler(
Expand Down Expand Up @@ -100,19 +102,27 @@ class MapboxHybridRouter(
/**
* This method is equivalent to calling .getRoute() with the additional parameter capture
*/
override fun execute(routeOptions: RouteOptions, clientCallback: Router.Callback) {
override fun getRoute(routeOptions: RouteOptions, clientCallback: Router.Callback) {
reserveRouterCalled = false
options = routeOptions
callback = clientCallback
mainRouter.getRoute(routeOptions, this)
}

override fun getRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback) {
mainRouter.getRouteRefresh(route, legIndex, callback)
}
}

override fun getRoute(
routeOptions: RouteOptions,
callback: Router.Callback
) {
routeDispatchHandler.get().execute(routeOptions, callback)
routeDispatchHandler.get().getRoute(routeOptions, callback)
}

override fun getRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback) {
routeDispatchHandler.get().getRouteRefresh(route, legIndex, callback)
}

override fun cancel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package com.mapbox.navigation.route.offboard
import android.content.Context
import com.mapbox.annotation.navigation.module.MapboxNavigationModule
import com.mapbox.annotation.navigation.module.MapboxNavigationModuleType
import com.mapbox.api.directions.v5.DirectionsCriteria
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.api.directionsrefresh.v1.MapboxDirectionsRefresh
import com.mapbox.navigation.base.accounts.SkuTokenProvider
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.RouteRefreshError
import com.mapbox.navigation.base.route.Router
import com.mapbox.navigation.route.offboard.router.routeOptions
import com.mapbox.navigation.route.offboard.routerefresh.RouteRefreshCallbackMapper
import com.mapbox.navigation.utils.exceptions.NavigationException
import retrofit2.Call
import retrofit2.Callback
Expand All @@ -32,13 +38,15 @@ class MapboxOffboardRouter(
}

private var mapboxDirections: MapboxDirections? = null
private var mapboxDirectionsRefresh: MapboxDirectionsRefresh? = null

override fun getRoute(
routeOptions: RouteOptions,
callback: Router.Callback
) {
mapboxDirections = RouteBuilderProvider.getBuilder(accessToken, context, skuTokenProvider)
.routeOptions(routeOptions)
.enableRefresh(routeOptions.profile() == DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
.build()
mapboxDirections?.enqueueCall(object : Callback<DirectionsResponse> {

Expand Down Expand Up @@ -67,5 +75,24 @@ class MapboxOffboardRouter(
override fun cancel() {
mapboxDirections?.cancelCall()
mapboxDirections = null

mapboxDirectionsRefresh?.cancelCall()
mapboxDirectionsRefresh = null
}

override fun getRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback) {
try {
val refreshBuilder = MapboxDirectionsRefresh.builder()
.accessToken(accessToken)
.requestId(route.routeOptions()?.requestUuid())
.legIndex(legIndex)

mapboxDirectionsRefresh = refreshBuilder.build()
mapboxDirectionsRefresh?.enqueueCall(RouteRefreshCallbackMapper(route, legIndex, callback))
} catch (throwable: Throwable) {
callback.onError(RouteRefreshError(
message = "Route refresh call failed",
throwable = throwable))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ internal object RouteBuilderProvider {
.accessToken(accessToken)
.voiceInstructions(true)
.bannerInstructions(true)
.enableRefresh(false)
.voiceUnits(context.inferDeviceLocale().getUnitTypeForLocale())
.interceptor {
val httpUrl = it.request().url()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mapbox.navigation.route.offboard.routerefresh

import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directionsrefresh.v1.models.DirectionsRefreshResponse
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.RouteRefreshError
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

internal class RouteRefreshCallbackMapper(
private val originalRoute: DirectionsRoute,
private val currentLegIndex: Int,
private val callback: RouteRefreshCallback
) : Callback<DirectionsRefreshResponse> {

override fun onResponse(call: Call<DirectionsRefreshResponse>, response: Response<DirectionsRefreshResponse>) {
val routeAnnotations = response.body()?.route()
var errorThrowable: Throwable? = null
val refreshedDirectionsRoute = try {
mapToDirectionsRoute(routeAnnotations)
} catch (t: Throwable) {
errorThrowable = t
null
}
if (refreshedDirectionsRoute != null) {
callback.onRefresh(refreshedDirectionsRoute)
} else {
callback.onError(RouteRefreshError(
message = "Failed to read refresh response",
throwable = errorThrowable))
}
}

override fun onFailure(call: Call<DirectionsRefreshResponse>, t: Throwable) {
callback.onError(RouteRefreshError(throwable = t))
}

private fun mapToDirectionsRoute(routeAnnotations: DirectionsRoute?): DirectionsRoute? {
val validRouteAnnotations = routeAnnotations ?: return null
val refreshedRouteLegs = originalRoute.legs()?.let { oldRouteLegsList ->
val legs = oldRouteLegsList.toMutableList()
for (i in currentLegIndex until legs.size) {
validRouteAnnotations.legs()?.let { annotationHolderRouteLegsList ->
val updatedAnnotation = annotationHolderRouteLegsList[i - currentLegIndex].annotation()
legs[i] = legs[i].toBuilder().annotation(updatedAnnotation).build()
}
}
legs.toList()
}
return originalRoute.toBuilder().legs(refreshedRouteLegs).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class MapboxOffboardRouterTest : BaseTest() {
every { mockSkuTokenProvider.obtainUrlWithSkuToken("/mock", 1) } returns ("/mock&sku=102jaksdhfj")
every { RouteBuilderProvider.getBuilder(accessToken, context, mockSkuTokenProvider) } returns mapboxDirectionsBuilder
every { mapboxDirectionsBuilder.interceptor(any()) } returns mapboxDirectionsBuilder
every { mapboxDirectionsBuilder.enableRefresh(any()) } returns mapboxDirectionsBuilder
every { mapboxDirectionsBuilder.build() } returns mapboxDirections
every { mapboxDirections.enqueueCall(capture(listener)) } answers {
callback = listener.captured
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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.navigation.base.options.MapboxOnboardRouterConfig
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.Router
import com.mapbox.navigation.base.route.internal.RouteUrl
import com.mapbox.navigation.navigator.MapboxNativeNavigator
Expand Down Expand Up @@ -114,6 +115,10 @@ class MapboxOnboardRouter(
mainJobControl.job.cancelChildren()
}

override fun getRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback) {
// Does nothing
}

private fun retrieveRoute(url: String, callback: Router.Callback) {
mainJobControl.scope.launch {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mapbox.navigation.base.route

import com.mapbox.api.directions.v5.models.DirectionsRoute

interface RouteRefreshCallback {
fun onRefresh(directionsRoute: DirectionsRoute)

fun onError(error: RouteRefreshError)
}

data class RouteRefreshError(
val message: String? = null,
val throwable: Throwable? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ interface Router {
*/
fun cancel()

/**
* Refresh the traffic annotations for a given [DirectionsRoute]
*
* @param route DirectionsRoute the direction route to refresh
* @param legIndex Int the index of the current leg in the route
* @param callback Callback that gets notified with the results of the request
*/
fun getRouteRefresh(
route: DirectionsRoute,
legIndex: Int,
callback: RouteRefreshCallback
)

/**
* Callback for Router fetching
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.fasterroute.FasterRouteController
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
import com.mapbox.navigation.core.module.NavigationModuleProvider
import com.mapbox.navigation.core.routerefresh.RouteRefreshController
import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry
import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry.TAG
import com.mapbox.navigation.core.telemetry.events.TelemetryUserFeedback
Expand Down Expand Up @@ -136,6 +137,7 @@ constructor(
private val internalRoutesObserver = createInternalRoutesObserver()
private val internalOffRouteObserver = createInternalOffRouteObserver()
private val fasterRouteController: FasterRouteController
private val routeRefreshController: RouteRefreshController

private var notificationChannelField: Field? = null
private val MAPBOX_NAVIGATION_NOTIFICATION_PACKAGE_NAME =
Expand Down Expand Up @@ -192,6 +194,8 @@ constructor(
}

fasterRouteController = FasterRouteController(directionsSession, tripSession)
routeRefreshController = RouteRefreshController(directionsSession, tripSession)
routeRefreshController.start()
}

/**
Expand Down Expand Up @@ -289,6 +293,7 @@ constructor(
tripSession.unregisterAllBannerInstructionsObservers()
tripSession.unregisterAllVoiceInstructionsObservers()
fasterRouteController.stop()
routeRefreshController.stop()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mapbox.navigation.core.directions.session

import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.Router

internal interface DirectionsSession {
Expand All @@ -22,6 +23,8 @@ internal interface DirectionsSession {
*/
fun requestFasterRoute(adjustedRouteOptions: RouteOptions, routesRequestCallback: RoutesRequestCallback)

fun requestRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback)

fun cancel()

fun registerRoutesObserver(routesObserver: RoutesObserver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.mapbox.navigation.core.directions.session
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.navigation.base.extensions.ifNonNull
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.Router
import java.util.concurrent.CopyOnWriteArrayList

Expand Down Expand Up @@ -33,6 +34,10 @@ class MapboxDirectionsSession(
router.cancel()
}

override fun requestRouteRefresh(route: DirectionsRoute, legIndex: Int, callback: RouteRefreshCallback) {
router.getRouteRefresh(route, legIndex, callback)
}

override fun requestRoutes(
routeOptions: RouteOptions,
routesRequestCallback: RoutesRequestCallback?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.mapbox.navigation.core.routerefresh

import android.util.Log
import com.mapbox.api.directions.v5.DirectionsCriteria
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.navigation.base.route.RouteRefreshCallback
import com.mapbox.navigation.base.route.RouteRefreshError
import com.mapbox.navigation.core.directions.session.DirectionsSession
import com.mapbox.navigation.core.trip.session.TripSession
import com.mapbox.navigation.utils.timer.MapboxTimer
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Job

/**
* This class is responsible for refreshing the current direction route's traffic.
* This does not support alternative routes.
*
* If the route is successfully refreshed, this class will update the [TripSession.route]
*
* [start] and [stop] are attached to the application lifecycle. Observing routes that
* can be refreshed are handled by this class. Calling [start] will restart the refresh timer.
*/
internal class RouteRefreshController(
private val directionsSession: DirectionsSession,
private val tripSession: TripSession
) {
private val routerRefreshTimer = MapboxTimer()

init {
routerRefreshTimer.restartAfterMillis = TimeUnit.MINUTES.toMillis(5)
}

fun start(): Job {
stop()
return routerRefreshTimer.startTimer {
val route = tripSession.route?.takeIf { supportsRefresh(it) }
route?.let {
val legIndex = tripSession.getRouteProgress()?.currentLegProgress()?.legIndex() ?: 0
directionsSession.requestRouteRefresh(
route,
legIndex,
routeRefreshCallback)
}
}
}

fun stop() {
routerRefreshTimer.stopJobs()
}

private fun supportsRefresh(route: DirectionsRoute?): Boolean {
val isTrafficProfile = route?.routeOptions()
?.profile()?.equals(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
return isTrafficProfile == true
}

private val routeRefreshCallback = object : RouteRefreshCallback {

override fun onRefresh(directionsRoute: DirectionsRoute) {
Log.i("RouteRefresh", "Successful refresh")
tripSession.route = directionsRoute
val directionsSessionRoutes = directionsSession.routes.toMutableList()
if (directionsSessionRoutes.isNotEmpty()) {
directionsSessionRoutes[0] = directionsRoute
directionsSession.routes = directionsSessionRoutes
}
}

override fun onError(error: RouteRefreshError) {
if (error.throwable != null) {
Log.e("RouteRefresh", error.message, error.throwable)
} else {
Log.e("RouteRefresh", error.message)
}
}
}
}

0 comments on commit 7c87897

Please sign in to comment.