Skip to content

Commit 41e81a7

Browse files
committed
NAVAND-552: predownload voice instructions
1 parent d4a714d commit 41e81a7

30 files changed

+1574
-349
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Mapbox welcomes participation and contributions from everyone.
55
## Unreleased
66
#### Features
77
#### Bug fixes and improvements
8+
- Optimized voice instructions downloading. [#6547](https://github.com/mapbox/mapbox-navigation-android/pull/6547)
9+
- Fixed an issue where voice instruction was being played too late with low connectivity. [#6547](https://github.com/mapbox/mapbox-navigation-android/pull/6547)
810

911
## Mapbox Navigation SDK 2.9.2 - 18 November, 2022
1012
### Changelog
@@ -268,6 +270,9 @@ This release depends on, and has been tested with, the following Mapbox dependen
268270
- Mapbox Java `v6.9.0-beta.2` ([release notes](https://github.com/mapbox/mapbox-java/releases/tag/v6.9.0-beta.2))
269271
- Mapbox Android Core `v5.0.2` ([release notes](https://github.com/mapbox/mapbox-events-android/releases/tag/core-5.0.2))
270272

273+
- Fixed an issue with `NavigationView` that caused road label position to not update in some cases. [#6531](https://github.com/mapbox/mapbox-navigation-android/pull/6531)
274+
- 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)
275+
- Fixed an issue with EV route refresh failing in cases where EV data updates are not provided. Now, the initial parameters from a route request will be used as a fallback. [#6534](https://github.com/mapbox/mapbox-navigation-android/pull/6534)
271276

272277
## Mapbox Navigation SDK 2.10.0-alpha.1 - 28 October, 2022
273278
### Changelog

examples/src/main/java/com/mapbox/navigation/examples/core/MapboxNavigationActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
456456
mapboxNavigation.onDestroy()
457457
maneuverApi.cancel()
458458
speechAPI.cancel()
459+
speechAPI.destroy()
459460
voiceInstructionsPlayer.shutdown()
460461
}
461462

examples/src/main/java/com/mapbox/navigation/examples/core/MapboxVoiceActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ class MapboxVoiceActivity : AppCompatActivity(), OnMapLongClickListener {
413413
mapboxReplayer.finish()
414414
mapboxNavigation.onDestroy()
415415
speechApi.cancel()
416+
speechApi.destroy()
416417
voiceInstructionsPlayer.shutdown()
417418
}
418419

libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import com.mapbox.navigation.core.directions.session.RoutesExtra
5454
import com.mapbox.navigation.core.directions.session.RoutesObserver
5555
import com.mapbox.navigation.core.history.MapboxHistoryReader
5656
import com.mapbox.navigation.core.history.MapboxHistoryRecorder
57+
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
5758
import com.mapbox.navigation.core.internal.ReachabilityService
5859
import com.mapbox.navigation.core.internal.telemetry.CustomEvent
5960
import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback
@@ -401,7 +402,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
401402
private set
402403

403404
init {
404-
if (hasInstance) {
405+
if (LegacyMapboxNavigationInstanceHolder.peek() != null) {
405406
throw IllegalStateException(
406407
"""
407408
A different MapboxNavigation instance already exists.
@@ -410,7 +411,6 @@ class MapboxNavigation @VisibleForTesting internal constructor(
410411
""".trimIndent()
411412
)
412413
}
413-
hasInstance = true
414414

415415
val config = NavigatorLoader.createConfig(
416416
navigationOptions.deviceProfile,
@@ -577,6 +577,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
577577
navigator.cache
578578
)
579579
roadObjectMatcher = RoadObjectMatcher(navigator)
580+
LegacyMapboxNavigationInstanceHolder.onCreated(this)
580581
}
581582

582583
/**
@@ -1135,7 +1136,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
11351136
routesPreviewController.unregisterAllRoutesPreviewObservers()
11361137

11371138
isDestroyed = true
1138-
hasInstance = false
1139+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
11391140
}
11401141

11411142
/**
@@ -1993,9 +1994,6 @@ class MapboxNavigation @VisibleForTesting internal constructor(
19931994

19941995
private companion object {
19951996

1996-
@Volatile
1997-
private var hasInstance = false
1998-
19991997
private const val LOG_CATEGORY = "MapboxNavigation"
20001998
private const val USER_AGENT: String = "MapboxNavigationNative"
20011999
private const val THREADS_COUNT = 2

libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigationProvider.kt

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mapbox.navigation.core
22

33
import androidx.annotation.UiThread
44
import com.mapbox.navigation.base.options.NavigationOptions
5+
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
56

67
/**
78
* Singleton responsible for ensuring there is only one MapboxNavigation instance.
@@ -11,8 +12,6 @@ import com.mapbox.navigation.base.options.NavigationOptions
1112
message = "Use MapboxNavigationApp to attach MapboxNavigation to lifecycles."
1213
)
1314
object MapboxNavigationProvider {
14-
@Volatile
15-
private var mapboxNavigation: MapboxNavigation? = null
1615

1716
/**
1817
* Create MapboxNavigation with provided options.
@@ -25,12 +24,8 @@ object MapboxNavigationProvider {
2524
message = "Set the navigation options with MapboxNavigationApp.setup"
2625
)
2726
fun create(navigationOptions: NavigationOptions): MapboxNavigation {
28-
mapboxNavigation?.onDestroy()
29-
mapboxNavigation = MapboxNavigation(
30-
navigationOptions
31-
)
32-
33-
return mapboxNavigation!!
27+
LegacyMapboxNavigationInstanceHolder.peek()?.onDestroy()
28+
return MapboxNavigation(navigationOptions)
3429
}
3530

3631
/**
@@ -48,7 +43,7 @@ object MapboxNavigationProvider {
4843
throw RuntimeException("Need to create MapboxNavigation before using it.")
4944
}
5045

51-
return mapboxNavigation!!
46+
return LegacyMapboxNavigationInstanceHolder.peek()!!
5247
}
5348

5449
/**
@@ -59,15 +54,14 @@ object MapboxNavigationProvider {
5954
message = "MapboxNavigationApp will determine when to destroy MapboxNavigation instances"
6055
)
6156
fun destroy() {
62-
mapboxNavigation?.onDestroy()
63-
mapboxNavigation = null
57+
LegacyMapboxNavigationInstanceHolder.peek()?.onDestroy()
6458
}
6559

6660
/**
6761
* Check if MapboxNavigation is created.
6862
*/
6963
@JvmStatic
7064
fun isCreated(): Boolean {
71-
return mapboxNavigation?.isDestroyed == false
65+
return LegacyMapboxNavigationInstanceHolder.peek()?.isDestroyed == false
7266
}
7367
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.mapbox.navigation.core.internal
2+
3+
import androidx.annotation.UiThread
4+
import androidx.annotation.VisibleForTesting
5+
import com.mapbox.navigation.core.MapboxNavigation
6+
import java.util.concurrent.CopyOnWriteArraySet
7+
8+
fun interface MapboxNavigationCreateObserver {
9+
10+
fun onCreated(mapboxNavigation: MapboxNavigation)
11+
}
12+
13+
@Deprecated("Used to keep track of MapboxNavigation instances created via deprecated methods")
14+
@UiThread
15+
object LegacyMapboxNavigationInstanceHolder {
16+
17+
@Volatile
18+
private var mapboxNavigation: MapboxNavigation? = null
19+
20+
private val createObservers = CopyOnWriteArraySet<MapboxNavigationCreateObserver>()
21+
22+
fun onCreated(instance: MapboxNavigation) {
23+
mapboxNavigation = instance
24+
createObservers.forEach { it.onCreated(instance) }
25+
}
26+
27+
fun onDestroyed() {
28+
mapboxNavigation = null
29+
}
30+
31+
fun peek(): MapboxNavigation? = mapboxNavigation
32+
33+
fun registerCreateObserver(observer: MapboxNavigationCreateObserver) {
34+
createObservers.add(observer)
35+
mapboxNavigation?.let { observer.onCreated(it) }
36+
}
37+
38+
fun unregisterCreateObserver(observer: MapboxNavigationCreateObserver) {
39+
createObservers.remove(observer)
40+
}
41+
42+
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
43+
fun unregisterAllCreateObservers() {
44+
createObservers.clear()
45+
}
46+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.mapbox.navigation.core
2+
3+
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
4+
import com.mapbox.navigation.core.internal.MapboxNavigationCreateObserver
5+
import com.mapbox.navigation.testing.LoggingFrontendTestRule
6+
import io.mockk.clearMocks
7+
import io.mockk.mockk
8+
import io.mockk.verify
9+
import org.junit.After
10+
import org.junit.Assert.assertEquals
11+
import org.junit.Assert.assertNull
12+
import org.junit.Before
13+
import org.junit.Rule
14+
import org.junit.Test
15+
import org.junit.runner.RunWith
16+
import org.robolectric.RobolectricTestRunner
17+
18+
@RunWith(RobolectricTestRunner::class)
19+
class LegacyMapboxNavigationInstanceHolderTest {
20+
21+
@get:Rule
22+
val loggerRule = LoggingFrontendTestRule()
23+
private val observer = mockk<MapboxNavigationCreateObserver>(relaxed = true)
24+
private val mapboxNavigation = mockk<MapboxNavigation>(relaxed = true)
25+
26+
@Before
27+
fun setUp() {
28+
LegacyMapboxNavigationInstanceHolder.unregisterAllCreateObservers()
29+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
30+
}
31+
32+
@After
33+
fun tearDown() {
34+
LegacyMapboxNavigationInstanceHolder.unregisterAllCreateObservers()
35+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
36+
}
37+
38+
@Test
39+
fun `observer is invoked on registration if has mapboxNavigation`() {
40+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
41+
42+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
43+
44+
verify(exactly = 1) { observer.onCreated(mapboxNavigation) }
45+
}
46+
47+
@Test
48+
fun `observer is not invoked on registration if has no mapboxNavigation`() {
49+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
50+
51+
verify(exactly = 0) { observer.onCreated(mapboxNavigation) }
52+
}
53+
54+
@Test
55+
fun `observer is not invoked on registration if has destroyed mapboxNavigation`() {
56+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
57+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
58+
59+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
60+
61+
verify(exactly = 0) { observer.onCreated(mapboxNavigation) }
62+
}
63+
64+
@Test
65+
fun `observer is invoked when mapboxNavigation is created`() {
66+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
67+
68+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
69+
70+
verify(exactly = 1) { observer.onCreated(mapboxNavigation) }
71+
}
72+
73+
@Test
74+
fun `observer is invoked when new mapboxNavigation is created`() {
75+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
76+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
77+
clearMocks(observer, answers = false)
78+
79+
val newNavigation = mockk<MapboxNavigation>(relaxed = true)
80+
LegacyMapboxNavigationInstanceHolder.onCreated(newNavigation)
81+
82+
verify(exactly = 1) { observer.onCreated(newNavigation) }
83+
}
84+
85+
@Test
86+
fun `removed observer is not invoked when mapboxNavigation is created`() {
87+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
88+
LegacyMapboxNavigationInstanceHolder.unregisterCreateObserver(observer)
89+
90+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
91+
92+
verify(exactly = 0) { observer.onCreated(any()) }
93+
}
94+
95+
@Test
96+
fun `multiple observers are invoked when new mapboxNavigation is created`() {
97+
val secondObserver = mockk<MapboxNavigationCreateObserver>(relaxed = true)
98+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
99+
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(secondObserver)
100+
101+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
102+
103+
verify(exactly = 1) {
104+
observer.onCreated(mapboxNavigation)
105+
secondObserver.onCreated(mapboxNavigation)
106+
}
107+
}
108+
109+
@Test
110+
fun `peek with no instance`() {
111+
assertNull(LegacyMapboxNavigationInstanceHolder.peek())
112+
}
113+
114+
@Test
115+
fun `peek with active instance`() {
116+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
117+
118+
assertEquals(mapboxNavigation, LegacyMapboxNavigationInstanceHolder.peek())
119+
}
120+
121+
@Test
122+
fun `peek with destroyed instance`() {
123+
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
124+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
125+
126+
assertNull(LegacyMapboxNavigationInstanceHolder.peek())
127+
}
128+
}

libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.mapbox.navigation.base.trip.notification.TripNotification
1919
import com.mapbox.navigation.core.accounts.BillingController
2020
import com.mapbox.navigation.core.arrival.ArrivalProgressObserver
2121
import com.mapbox.navigation.core.directions.session.DirectionsSession
22+
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
2223
import com.mapbox.navigation.core.navigator.CacheHandleWrapper
2324
import com.mapbox.navigation.core.preview.RoutesPreviewController
2425
import com.mapbox.navigation.core.reroute.RerouteController
@@ -128,6 +129,7 @@ internal open class MapboxNavigationBaseTest {
128129

129130
@Before
130131
open fun setUp() {
132+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
131133
every { threadController.getMainScopeAndRootJob() } answers {
132134
JobControl(mockk(), coroutineRule.createTestScope())
133135
}
@@ -243,6 +245,7 @@ internal open class MapboxNavigationBaseTest {
243245
unmockkObject(EventsServiceProvider)
244246
unmockkObject(TelemetryServiceProvider)
245247
unmockkObject(TelemetryUtilsDelegate)
248+
LegacyMapboxNavigationInstanceHolder.onDestroyed()
246249
}
247250

248251
fun createMapboxNavigation() {

libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.mapbox.navigation.core.directions.session.RoutesExtra
1717
import com.mapbox.navigation.core.directions.session.RoutesObserver
1818
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
1919
import com.mapbox.navigation.core.internal.HistoryRecordingStateChangeObserver
20+
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
2021
import com.mapbox.navigation.core.internal.extensions.registerHistoryRecordingStateChangeObserver
2122
import com.mapbox.navigation.core.internal.extensions.unregisterHistoryRecordingStateChangeObserver
2223
import com.mapbox.navigation.core.internal.telemetry.NavigationCustomEventType
@@ -55,6 +56,7 @@ import io.mockk.coVerifyOrder
5556
import io.mockk.every
5657
import io.mockk.just
5758
import io.mockk.mockk
59+
import io.mockk.mockkObject
5860
import io.mockk.slot
5961
import io.mockk.verify
6062
import io.mockk.verifyOrder
@@ -1757,4 +1759,32 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() {
17571759
evDataHolder.updateData(data)
17581760
}
17591761
}
1762+
1763+
@Test
1764+
fun mapboxNavigationCreationSetsInstanceToHolder() {
1765+
mockkObject(LegacyMapboxNavigationInstanceHolder) {
1766+
createMapboxNavigation()
1767+
verify { LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation) }
1768+
}
1769+
}
1770+
1771+
@Test(expected = IllegalStateException::class)
1772+
fun mapboxNavigationIsNotCreatedIfHolderHasInstance() {
1773+
mockkObject(LegacyMapboxNavigationInstanceHolder) {
1774+
every { LegacyMapboxNavigationInstanceHolder.peek() } returns mockk(relaxed = true)
1775+
createMapboxNavigation()
1776+
verify(exactly = 0) {
1777+
LegacyMapboxNavigationInstanceHolder.onCreated(any())
1778+
}
1779+
}
1780+
}
1781+
1782+
@Test
1783+
fun mapboxNavigationDestructionRemovesInstanceFromHolder() {
1784+
mockkObject(LegacyMapboxNavigationInstanceHolder) {
1785+
createMapboxNavigation()
1786+
mapboxNavigation.onDestroy()
1787+
verify { LegacyMapboxNavigationInstanceHolder.onDestroyed() }
1788+
}
1789+
}
17601790
}

0 commit comments

Comments
 (0)