Skip to content

Commit b855099

Browse files
committed
NAVAND-777: add route refresh on demand
1 parent eaffcea commit b855099

File tree

41 files changed

+8288
-1908
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+8288
-1908
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Mapbox welcomes participation and contributions from everyone.
44

55
## Unreleased
66
#### Features
7+
- Added `MapboxNavigation#refreshRoutesImmediately` to trigger route refresh request immediately. [#6610](https://github.com/mapbox/mapbox-navigation-android/pull/6610)
78
#### Bug fixes and improvements
89

910
## Mapbox Navigation SDK 2.10.0-beta.1 - 18 November, 2022
@@ -188,6 +189,7 @@ This release depends on, and has been tested with, the following Mapbox dependen
188189
[Changes between v2.10.0-alpha.1 and v2.10.0-alpha.2](https://github.com/mapbox/mapbox-navigation-android/compare/v2.10.0-alpha.1...v2.10.0-alpha.2)
189190

190191
#### Features
192+
- Added `MapboxNavigation#refreshRoutesImmediately` to request an immediate refresh of current routes.
191193
#### Bug fixes and improvements
192194
- Fixed an issue where "silent waypoints" (not regular waypoints that define legs) had markers added on the map when route line was drawn with `MapboxRouteLineApi` and `MapboxRouteLineView`. [#6526](https://github.com/mapbox/mapbox-navigation-android/pull/6526)
193195
- Fixed an issue where `DirectionsResponse#waypoints` list was cleared after a successful non-EV route refresh. [#6539](https://github.com/mapbox/mapbox-navigation-android/pull/6539)

instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/EVRouteRefreshTest.kt

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import androidx.annotation.IdRes
55
import com.mapbox.api.directions.v5.DirectionsCriteria
66
import com.mapbox.api.directions.v5.models.DirectionsWaypoint
77
import com.mapbox.api.directions.v5.models.RouteOptions
8-
import com.mapbox.api.directionsrefresh.v1.models.DirectionsRefreshResponse
98
import com.mapbox.geojson.Point
109
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1110
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
@@ -20,6 +19,7 @@ import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
2019
import com.mapbox.navigation.core.internal.extensions.flowLocationMatcherResult
2120
import com.mapbox.navigation.instrumentation_tests.R
2221
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
22+
import com.mapbox.navigation.instrumentation_tests.utils.DynamicResponseModifier
2323
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
2424
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.getSuccessfulResultOrThrowException
2525
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.requestRoutes
@@ -662,39 +662,3 @@ class EVRouteRefreshTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.
662662
mockWebServerRule.requestHandlers.add(0, routeHandler)
663663
}
664664
}
665-
666-
private class DynamicResponseModifier : (String) -> String {
667-
668-
var numberOfInvocations = 0
669-
670-
override fun invoke(p1: String): String {
671-
numberOfInvocations++
672-
val originalResponse = DirectionsRefreshResponse.fromJson(p1)
673-
val newRoute = originalResponse.route()!!
674-
.toBuilder()
675-
.legs(
676-
originalResponse.route()!!.legs()!!.map {
677-
it
678-
.toBuilder()
679-
.annotation(
680-
it.annotation()!!
681-
.toBuilder()
682-
.speed(
683-
it.annotation()!!.speed()!!.map {
684-
it + numberOfInvocations * 0.1
685-
}
686-
)
687-
.build()
688-
)
689-
.build()
690-
}
691-
)
692-
.build()
693-
return DirectionsRefreshResponse.builder()
694-
.route(newRoute)
695-
.code(originalResponse.code())
696-
.message(originalResponse.message())
697-
.build()
698-
.toJson()
699-
}
700-
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package com.mapbox.navigation.instrumentation_tests.core
2+
3+
import android.location.Location
4+
import androidx.annotation.IntegerRes
5+
import com.mapbox.api.directions.v5.DirectionsCriteria
6+
import com.mapbox.api.directions.v5.models.RouteOptions
7+
import com.mapbox.geojson.Point
8+
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
9+
import com.mapbox.navigation.base.options.NavigationOptions
10+
import com.mapbox.navigation.base.options.RoutingTilesOptions
11+
import com.mapbox.navigation.base.route.NavigationRoute
12+
import com.mapbox.navigation.base.route.RouteRefreshOptions
13+
import com.mapbox.navigation.core.MapboxNavigation
14+
import com.mapbox.navigation.core.MapboxNavigationProvider
15+
import com.mapbox.navigation.core.directions.session.RoutesExtra
16+
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
17+
import com.mapbox.navigation.instrumentation_tests.R
18+
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
19+
import com.mapbox.navigation.instrumentation_tests.utils.DynamicResponseModifier
20+
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
21+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.getSuccessfulResultOrThrowException
22+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.requestRoutes
23+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesUpdates
24+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.sdkTest
25+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.setNavigationRoutesAndWaitForUpdate
26+
import com.mapbox.navigation.instrumentation_tests.utils.http.FailByRequestMockRequestHandler
27+
import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRefreshHandler
28+
import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRequestHandler
29+
import com.mapbox.navigation.instrumentation_tests.utils.http.MockRoutingTileEndpointErrorRequestHandler
30+
import com.mapbox.navigation.instrumentation_tests.utils.location.MockLocationReplayerRule
31+
import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText
32+
import com.mapbox.navigation.testing.ui.BaseTest
33+
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
34+
import kotlinx.coroutines.delay
35+
import kotlinx.coroutines.flow.filter
36+
import kotlinx.coroutines.flow.first
37+
import kotlinx.coroutines.flow.take
38+
import kotlinx.coroutines.flow.toList
39+
import org.junit.Assert.assertEquals
40+
import org.junit.Before
41+
import org.junit.Rule
42+
import org.junit.Test
43+
import java.net.URI
44+
import java.util.concurrent.TimeUnit
45+
46+
class RouteRefreshOnDemandTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {
47+
48+
@get:Rule
49+
val mapboxNavigationRule = MapboxNavigationRule()
50+
51+
@get:Rule
52+
val mockLocationReplayerRule = MockLocationReplayerRule(mockLocationUpdatesRule)
53+
54+
private lateinit var refreshHandler: MockDirectionsRefreshHandler
55+
private lateinit var mapboxNavigation: MapboxNavigation
56+
private val twoCoordinates = listOf(
57+
Point.fromLngLat(-121.496066, 38.577764),
58+
Point.fromLngLat(-121.480279, 38.57674)
59+
)
60+
61+
override fun setupMockLocation(): Location = mockLocationUpdatesRule.generateLocationUpdate {
62+
latitude = twoCoordinates[0].latitude()
63+
longitude = twoCoordinates[0].longitude()
64+
bearing = 190f
65+
}
66+
67+
@Before
68+
fun setup() {
69+
setupMockRequestHandlers(
70+
twoCoordinates,
71+
R.raw.route_response_route_refresh,
72+
R.raw.route_response_route_refresh_annotations,
73+
"route_response_route_refresh"
74+
)
75+
}
76+
77+
@Test
78+
fun route_refresh_on_demand_executes_before_refresh_interval() = sdkTest {
79+
val routeRefreshOptions = RouteRefreshOptions.Builder()
80+
.intervalMillis(TimeUnit.MINUTES.toMillis(1))
81+
.build()
82+
createMapboxNavigation(routeRefreshOptions)
83+
val routeOptions = generateRouteOptions(twoCoordinates)
84+
val requestedRoutes = mapboxNavigation.requestRoutes(routeOptions)
85+
.getSuccessfulResultOrThrowException()
86+
.routes
87+
mapboxNavigation.startTripSession()
88+
stayOnInitialPosition()
89+
mapboxNavigation.setNavigationRoutesAndWaitForUpdate(requestedRoutes)
90+
91+
mapboxNavigation.refreshRoutesImmediately()
92+
val refreshedRoutes = mapboxNavigation.routesUpdates()
93+
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
94+
.first()
95+
96+
assertEquals(
97+
224.2239,
98+
requestedRoutes[0].getSumOfDurationAnnotationsFromLeg(0),
99+
0.0001
100+
)
101+
assertEquals(
102+
258.767,
103+
refreshedRoutes.navigationRoutes[0].getSumOfDurationAnnotationsFromLeg(0),
104+
0.0001
105+
)
106+
}
107+
108+
@Test
109+
fun route_refresh_on_demand_invalidates_planned_timer() = sdkTest {
110+
val routeRefreshes = mutableListOf<RoutesUpdatedResult>()
111+
val routeRefreshOptions = RouteRefreshOptions.Builder()
112+
.intervalMillis(TimeUnit.SECONDS.toMillis(30))
113+
.build()
114+
RouteRefreshOptions::class.java.getDeclaredField("intervalMillis").apply {
115+
isAccessible = true
116+
set(routeRefreshOptions, 10_000L)
117+
}
118+
refreshHandler.jsonResponseModifier = DynamicResponseModifier()
119+
createMapboxNavigation(routeRefreshOptions)
120+
val routeOptions = generateRouteOptions(twoCoordinates)
121+
val requestedRoutes = mapboxNavigation.requestRoutes(routeOptions)
122+
.getSuccessfulResultOrThrowException()
123+
.routes
124+
mapboxNavigation.startTripSession()
125+
stayOnInitialPosition()
126+
mapboxNavigation.registerRoutesObserver {
127+
if (it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH) {
128+
routeRefreshes.add(it)
129+
}
130+
}
131+
mapboxNavigation.setNavigationRoutesAndWaitForUpdate(requestedRoutes)
132+
delay(5000)
133+
134+
mapboxNavigation.refreshRoutesImmediately()
135+
mapboxNavigation.routesUpdates()
136+
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
137+
.first()
138+
assertEquals(1, routeRefreshes.size)
139+
140+
// no route refresh 6 seconds after refresh on demand
141+
delay(6000)
142+
assertEquals(1, routeRefreshes.size)
143+
144+
delay(4000)
145+
// has new refresh 10 seconds after refresh on demand
146+
mapboxNavigation.routesUpdates()
147+
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
148+
.take(2)
149+
.toList()
150+
}
151+
152+
private fun createMapboxNavigation(routeRefreshOptions: RouteRefreshOptions) {
153+
mapboxNavigation = MapboxNavigationProvider.create(
154+
NavigationOptions.Builder(activity)
155+
.accessToken(getMapboxAccessTokenFromResources(activity))
156+
.routeRefreshOptions(routeRefreshOptions)
157+
.routingTilesOptions(
158+
RoutingTilesOptions.Builder()
159+
.tilesBaseUri(URI(mockWebServerRule.baseUrl))
160+
.build()
161+
)
162+
.navigatorPredictionMillis(0L)
163+
.build()
164+
)
165+
}
166+
167+
private fun stayOnInitialPosition() {
168+
mockLocationReplayerRule.loopUpdate(
169+
mockLocationUpdatesRule.generateLocationUpdate {
170+
latitude = twoCoordinates[0].latitude()
171+
longitude = twoCoordinates[0].longitude()
172+
bearing = 190f
173+
},
174+
times = 120
175+
)
176+
}
177+
178+
private fun generateRouteOptions(coordinates: List<Point>): RouteOptions {
179+
return RouteOptions.builder().applyDefaultNavigationOptions()
180+
.profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
181+
.alternatives(true)
182+
.coordinatesList(coordinates)
183+
.baseUrl(mockWebServerRule.baseUrl) // Comment out to test a real server
184+
.build()
185+
}
186+
187+
private fun setupMockRequestHandlers(
188+
coordinates: List<Point>,
189+
@IntegerRes routesResponse: Int,
190+
@IntegerRes refreshResponse: Int,
191+
responseTestUuid: String,
192+
acceptedGeometryIndex: Int? = null,
193+
) {
194+
mockWebServerRule.requestHandlers.clear()
195+
mockWebServerRule.requestHandlers.add(
196+
MockDirectionsRequestHandler(
197+
"driving-traffic",
198+
readRawFileText(activity, routesResponse),
199+
coordinates
200+
)
201+
)
202+
refreshHandler = MockDirectionsRefreshHandler(
203+
responseTestUuid,
204+
readRawFileText(activity, refreshResponse),
205+
acceptedGeometryIndex
206+
)
207+
mockWebServerRule.requestHandlers.add(FailByRequestMockRequestHandler(refreshHandler))
208+
mockWebServerRule.requestHandlers.add(MockRoutingTileEndpointErrorRequestHandler())
209+
}
210+
211+
private fun NavigationRoute.getSumOfDurationAnnotationsFromLeg(legIndex: Int): Double =
212+
directionsRoute.legs()?.get(legIndex)
213+
?.annotation()
214+
?.duration()
215+
?.sum()!!
216+
}

0 commit comments

Comments
 (0)