Skip to content

Commit 40e823b

Browse files
dzinadRingerJK
authored andcommitted
NAVAND-713: receive EV data updates from client
1 parent a45f7d4 commit 40e823b

File tree

31 files changed

+942
-247
lines changed

31 files changed

+942
-247
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
package com.mapbox.navigation.instrumentation_tests.core
2+
3+
import android.location.Location
4+
import com.mapbox.api.directions.v5.DirectionsCriteria
5+
import com.mapbox.api.directions.v5.models.RouteOptions
6+
import com.mapbox.api.directionsrefresh.v1.models.DirectionsRefreshResponse
7+
import com.mapbox.geojson.Point
8+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
9+
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
10+
import com.mapbox.navigation.base.options.NavigationOptions
11+
import com.mapbox.navigation.base.options.RoutingTilesOptions
12+
import com.mapbox.navigation.base.route.NavigationRoute
13+
import com.mapbox.navigation.base.route.RouteRefreshOptions
14+
import com.mapbox.navigation.core.EVDataObserver
15+
import com.mapbox.navigation.core.EVDataUpdater
16+
import com.mapbox.navigation.core.MapboxNavigation
17+
import com.mapbox.navigation.core.MapboxNavigationProvider
18+
import com.mapbox.navigation.core.directions.session.RoutesExtra
19+
import com.mapbox.navigation.instrumentation_tests.R
20+
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
21+
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
22+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.getSuccessfulResultOrThrowException
23+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.requestRoutes
24+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesUpdates
25+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.sdkTest
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.location.MockLocationReplayerRule
30+
import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText
31+
import com.mapbox.navigation.testing.ui.BaseTest
32+
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
33+
import com.mapbox.navigation.testing.ui.utils.runOnMainSync
34+
import kotlinx.coroutines.flow.filter
35+
import kotlinx.coroutines.flow.first
36+
import kotlinx.coroutines.flow.take
37+
import kotlinx.coroutines.flow.toList
38+
import okhttp3.HttpUrl
39+
import org.junit.Assert.assertEquals
40+
import org.junit.Assert.assertFalse
41+
import org.junit.Before
42+
import org.junit.Rule
43+
import org.junit.Test
44+
import java.net.URI
45+
import java.util.concurrent.CopyOnWriteArraySet
46+
import java.util.concurrent.TimeUnit
47+
48+
private const val KEY_ENGINE = "engine"
49+
private const val KEY_ENERGY_CONSUMPTION_CURVE = "energy_consumption_curve"
50+
private const val KEY_EV_INITIAL_CHARGE = "ev_initial_charge"
51+
private const val KEY_AUXILIARY_CONSUMPTION = "auxiliary_consumption"
52+
private const val KEY_EV_PRECONDITIONING_TIME = "ev_pre_conditioning_time"
53+
private const val VALUE_ELECTRIC = "electric"
54+
private val evDataKeys = setOf(
55+
KEY_EV_INITIAL_CHARGE,
56+
KEY_ENERGY_CONSUMPTION_CURVE,
57+
KEY_EV_PRECONDITIONING_TIME,
58+
KEY_AUXILIARY_CONSUMPTION
59+
)
60+
61+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
62+
class EVRouteRefreshTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {
63+
64+
@get:Rule
65+
val mapboxNavigationRule = MapboxNavigationRule()
66+
67+
@get:Rule
68+
val mockLocationReplayerRule = MockLocationReplayerRule(mockLocationUpdatesRule)
69+
70+
private lateinit var mapboxNavigation: MapboxNavigation
71+
private val responseTestUuid = "route_response_route_refresh"
72+
private val twoCoordinates = listOf(
73+
Point.fromLngLat(-121.496066, 38.577764),
74+
Point.fromLngLat(-121.480279, 38.57674)
75+
)
76+
private lateinit var refreshHandler: MockDirectionsRefreshHandler
77+
private val evDataUpdater = TestEVDataUpdater()
78+
79+
override fun setupMockLocation(): Location = mockLocationUpdatesRule.generateLocationUpdate {
80+
latitude = twoCoordinates[0].latitude()
81+
longitude = twoCoordinates[0].longitude()
82+
bearing = 190f
83+
}
84+
85+
@Before
86+
fun setup() {
87+
runOnMainSync {
88+
val routeRefreshOptions = RouteRefreshOptions.Builder()
89+
.intervalMillis(TimeUnit.SECONDS.toMillis(30))
90+
.build()
91+
RouteRefreshOptions::class.java.getDeclaredField("intervalMillis").apply {
92+
isAccessible = true
93+
set(routeRefreshOptions, 1_500L)
94+
}
95+
mapboxNavigation = MapboxNavigationProvider.create(
96+
NavigationOptions.Builder(activity)
97+
.accessToken(getMapboxAccessTokenFromResources(activity))
98+
.routeRefreshOptions(routeRefreshOptions)
99+
.routingTilesOptions(
100+
RoutingTilesOptions.Builder()
101+
.tilesBaseUri(URI(mockWebServerRule.baseUrl))
102+
.build()
103+
)
104+
.navigatorPredictionMillis(0L)
105+
.build()
106+
)
107+
mockWebServerRule.requestHandlers.clear()
108+
mockWebServerRule.requestHandlers.add(
109+
MockDirectionsRequestHandler(
110+
"driving-traffic",
111+
readRawFileText(activity, R.raw.route_response_route_refresh),
112+
twoCoordinates
113+
)
114+
)
115+
refreshHandler = MockDirectionsRefreshHandler(
116+
responseTestUuid,
117+
readRawFileText(activity, R.raw.route_response_route_refresh_annotations),
118+
)
119+
mockWebServerRule.requestHandlers.add(FailByRequestMockRequestHandler(refreshHandler))
120+
}
121+
}
122+
123+
@Test
124+
fun evRouteRefreshParametersForNonEVRoute() = sdkTest {
125+
val requestedRoutes = requestRoutes(twoCoordinates, engine = null)
126+
127+
mapboxNavigation.setEVDataUpdater(evDataUpdater)
128+
evDataUpdater.updateData(mapOf(
129+
KEY_ENERGY_CONSUMPTION_CURVE to "0,300;20,120;40,150",
130+
KEY_EV_INITIAL_CHARGE to "80",
131+
KEY_EV_PRECONDITIONING_TIME to "10",
132+
KEY_AUXILIARY_CONSUMPTION to "300"
133+
))
134+
mapboxNavigation.setNavigationRoutes(requestedRoutes)
135+
mapboxNavigation.startTripSession()
136+
stayOnInitialPosition()
137+
waitUntilRefresh()
138+
139+
checkDoesNotHaveParameters(
140+
refreshHandler.handledRequests.first().requestUrl!!,
141+
evDataKeys + KEY_ENGINE
142+
)
143+
}
144+
145+
@Test
146+
fun evRouteRefreshParametersForEVRouteWithEVNoData() = sdkTest {
147+
val requestedRoutes = requestRoutes(twoCoordinates, engine = VALUE_ELECTRIC)
148+
149+
mapboxNavigation.setNavigationRoutes(requestedRoutes)
150+
mapboxNavigation.startTripSession()
151+
stayOnInitialPosition()
152+
waitUntilRefresh()
153+
154+
checkDoesNotHaveParameters(
155+
refreshHandler.handledRequests.first().requestUrl!!,
156+
evDataKeys
157+
)
158+
checkHasParameters(
159+
refreshHandler.handledRequests.first().requestUrl!!,
160+
mapOf(KEY_ENGINE to VALUE_ELECTRIC)
161+
)
162+
}
163+
164+
@Test
165+
fun evRouteRefreshParametersForEVRouteWithEVData() = sdkTest {
166+
val requestedRoutes = requestRoutes(twoCoordinates, engine = VALUE_ELECTRIC)
167+
168+
val consumptionCurve = "0,300;20,120;40,150"
169+
val initialCharge = "80"
170+
val preconditioningTime = "10"
171+
val auxiliaryConsumption = "300"
172+
mapboxNavigation.setEVDataUpdater(evDataUpdater)
173+
val evData = mapOf(
174+
KEY_ENERGY_CONSUMPTION_CURVE to consumptionCurve,
175+
KEY_EV_INITIAL_CHARGE to initialCharge,
176+
KEY_EV_PRECONDITIONING_TIME to preconditioningTime,
177+
KEY_AUXILIARY_CONSUMPTION to auxiliaryConsumption
178+
)
179+
evDataUpdater.updateData(evData)
180+
181+
mapboxNavigation.setNavigationRoutes(requestedRoutes)
182+
mapboxNavigation.startTripSession()
183+
stayOnInitialPosition()
184+
waitUntilRefresh()
185+
186+
checkHasParameters(
187+
refreshHandler.handledRequests.first().requestUrl!!,
188+
evData + (KEY_ENGINE to VALUE_ELECTRIC)
189+
)
190+
}
191+
192+
@Test
193+
fun evRouteRefreshParametersForEVRouteWithEVDataUpdates() = sdkTest {
194+
refreshHandler.jsonResponseModifier = DynamicResponseModifier()
195+
val requestedRoutes = requestRoutes(twoCoordinates, engine = VALUE_ELECTRIC)
196+
197+
mapboxNavigation.setNavigationRoutes(requestedRoutes)
198+
mapboxNavigation.startTripSession()
199+
stayOnInitialPosition()
200+
waitUntilRefresh()
201+
202+
val noUpdaterRefreshUrl = refreshHandler.handledRequests.first().requestUrl!!
203+
checkDoesNotHaveParameters(noUpdaterRefreshUrl, evDataKeys)
204+
checkHasParameters(noUpdaterRefreshUrl, mapOf(KEY_ENGINE to VALUE_ELECTRIC))
205+
206+
val consumptionCurve = "0,300;20,120;40,150"
207+
val initialCharge = "80"
208+
val preconditioningTime = "10"
209+
val auxiliaryConsumption = "300"
210+
val firstEvData = mapOf(
211+
KEY_ENERGY_CONSUMPTION_CURVE to consumptionCurve,
212+
KEY_EV_INITIAL_CHARGE to initialCharge,
213+
KEY_EV_PRECONDITIONING_TIME to preconditioningTime,
214+
KEY_AUXILIARY_CONSUMPTION to auxiliaryConsumption
215+
)
216+
mapboxNavigation.setEVDataUpdater(evDataUpdater)
217+
evDataUpdater.updateData(firstEvData)
218+
waitUntilNewRefresh()
219+
220+
checkHasParameters(
221+
refreshHandler.handledRequests.last().requestUrl!!,
222+
firstEvData + (KEY_ENGINE to VALUE_ELECTRIC)
223+
)
224+
225+
val newInitialCharge = "60"
226+
evDataUpdater.updateData(
227+
mapOf(
228+
KEY_EV_INITIAL_CHARGE to newInitialCharge,
229+
KEY_EV_PRECONDITIONING_TIME to null,
230+
)
231+
)
232+
waitUntilNewRefresh()
233+
234+
val urlWithTwiceUpdatedData = refreshHandler.handledRequests.last().requestUrl!!
235+
checkHasParameters(
236+
urlWithTwiceUpdatedData,
237+
mapOf(
238+
KEY_ENGINE to VALUE_ELECTRIC,
239+
KEY_ENERGY_CONSUMPTION_CURVE to consumptionCurve,
240+
KEY_EV_INITIAL_CHARGE to newInitialCharge,
241+
KEY_AUXILIARY_CONSUMPTION to auxiliaryConsumption
242+
)
243+
)
244+
checkDoesNotHaveParameters(urlWithTwiceUpdatedData, setOf(KEY_EV_PRECONDITIONING_TIME))
245+
246+
mapboxNavigation.setEVDataUpdater(null)
247+
waitUntilNewRefresh()
248+
249+
val removedUpdaterRefreshUrl = refreshHandler.handledRequests.last().requestUrl!!
250+
checkHasParameters(
251+
removedUpdaterRefreshUrl,
252+
mapOf(
253+
KEY_ENGINE to VALUE_ELECTRIC,
254+
KEY_ENERGY_CONSUMPTION_CURVE to consumptionCurve,
255+
KEY_EV_INITIAL_CHARGE to newInitialCharge,
256+
KEY_AUXILIARY_CONSUMPTION to auxiliaryConsumption
257+
)
258+
)
259+
checkDoesNotHaveParameters(removedUpdaterRefreshUrl, setOf(KEY_EV_PRECONDITIONING_TIME))
260+
261+
val newUpdater = TestEVDataUpdater()
262+
mapboxNavigation.setEVDataUpdater(newUpdater)
263+
val newUpdaterCharge = "45"
264+
evDataUpdater.updateData(mapOf(KEY_EV_INITIAL_CHARGE to "50"))
265+
newUpdater.updateData(mapOf(KEY_EV_INITIAL_CHARGE to newUpdaterCharge))
266+
waitUntilNewRefresh()
267+
268+
checkHasParameters(
269+
refreshHandler.handledRequests.last().requestUrl!!,
270+
mapOf(KEY_EV_INITIAL_CHARGE to newUpdaterCharge)
271+
)
272+
273+
newUpdater.updateData(emptyMap())
274+
waitUntilNewRefresh()
275+
276+
val urlAfterEmptyUpdate = refreshHandler.handledRequests.last().requestUrl!!
277+
checkHasParameters(
278+
urlAfterEmptyUpdate,
279+
mapOf(
280+
KEY_ENGINE to VALUE_ELECTRIC,
281+
KEY_ENERGY_CONSUMPTION_CURVE to consumptionCurve,
282+
KEY_EV_INITIAL_CHARGE to newUpdaterCharge,
283+
KEY_AUXILIARY_CONSUMPTION to auxiliaryConsumption
284+
)
285+
)
286+
checkDoesNotHaveParameters(urlAfterEmptyUpdate, setOf(KEY_EV_PRECONDITIONING_TIME))
287+
}
288+
289+
private fun stayOnInitialPosition() {
290+
mockLocationReplayerRule.loopUpdate(
291+
mockLocationUpdatesRule.generateLocationUpdate {
292+
latitude = twoCoordinates[0].latitude()
293+
longitude = twoCoordinates[0].longitude()
294+
bearing = 190f
295+
},
296+
times = 120
297+
)
298+
}
299+
300+
private fun generateRouteOptions(coordinates: List<Point>, engine: String?): RouteOptions {
301+
return RouteOptions.builder().applyDefaultNavigationOptions()
302+
.profile(DirectionsCriteria.PROFILE_DRIVING_TRAFFIC)
303+
.alternatives(true)
304+
.coordinatesList(coordinates)
305+
.baseUrl(mockWebServerRule.baseUrl) // Comment out to test a real server
306+
.apply {
307+
if (engine != null) {
308+
unrecognizedProperties(mapOf(KEY_ENGINE to engine))
309+
}
310+
}
311+
.build()
312+
}
313+
314+
private suspend fun waitUntilRefresh() {
315+
mapboxNavigation.routesUpdates()
316+
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
317+
.first()
318+
}
319+
320+
private suspend fun waitUntilNewRefresh() {
321+
mapboxNavigation.routesUpdates()
322+
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
323+
.take(2)
324+
.toList()
325+
}
326+
327+
private suspend fun requestRoutes(coordinates: List<Point>, engine: String?): List<NavigationRoute> {
328+
return mapboxNavigation.requestRoutes(generateRouteOptions(coordinates, engine))
329+
.getSuccessfulResultOrThrowException()
330+
.routes
331+
}
332+
333+
private fun checkHasParameters(url: HttpUrl, expectedParameters: Map<String, String>) {
334+
expectedParameters.forEach {
335+
assertEquals("parameter ${it.key}=${it.value}", it.value, url.queryParameter(it.key))
336+
}
337+
}
338+
339+
private fun checkDoesNotHaveParameters(url: HttpUrl, forbiddenNames: Set<String>) {
340+
forbiddenNames.forEach {
341+
assertFalse("parameter $it", it in url.queryParameterNames)
342+
}
343+
}
344+
}
345+
346+
private class DynamicResponseModifier: (String) -> String {
347+
348+
var numberOfInvocations = 0
349+
350+
override fun invoke(p1: String): String {
351+
numberOfInvocations++
352+
val originalResponse = DirectionsRefreshResponse.fromJson(p1)
353+
val newRoute = originalResponse.route()!!
354+
.toBuilder()
355+
.legs(originalResponse.route()!!.legs()!!.map {
356+
it
357+
.toBuilder()
358+
.annotation(it.annotation()!!
359+
.toBuilder()
360+
.speed(it.annotation()!!.speed()!!.map { it + numberOfInvocations * 0.1 })
361+
.build()
362+
)
363+
.build()
364+
})
365+
.build()
366+
return DirectionsRefreshResponse.builder()
367+
.route(newRoute)
368+
.code(originalResponse.code())
369+
.message(originalResponse.message())
370+
.build()
371+
.toJson()
372+
}
373+
374+
}
375+
376+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
377+
private class TestEVDataUpdater : EVDataUpdater {
378+
379+
private val observers = CopyOnWriteArraySet<EVDataObserver>()
380+
381+
override fun registerEVDataObserver(observer: EVDataObserver) {
382+
observers.add(observer)
383+
}
384+
385+
override fun unregisterEVDataObserver(observer: EVDataObserver) {
386+
observers.remove(observer)
387+
}
388+
389+
fun updateData(data: Map<String, String?>) {
390+
observers.forEach { it.onEVDataUpdated(data) }
391+
}
392+
}

0 commit comments

Comments
 (0)