Skip to content
Closed
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Mapbox welcomes participation and contributions from everyone.
.build()
}
```
- Optimized voice instructions downloading. [#6547](https://github.com/mapbox/mapbox-navigation-android/pull/6547)
- Fixed an issue where voice instruction was being played too late with low connectivity. [#6547](https://github.com/mapbox/mapbox-navigation-android/pull/6547)

## Mapbox Navigation SDK 2.10.0-rc.1 - 16 December, 2022
### Changelog
Expand Down Expand Up @@ -501,6 +503,10 @@ This release depends on, and has been tested with, the following Mapbox dependen
- Mapbox Java `v6.9.0-beta.2` ([release notes](https://github.com/mapbox/mapbox-java/releases/tag/v6.9.0-beta.2))
- Mapbox Android Core `v5.0.2` ([release notes](https://github.com/mapbox/mapbox-events-android/releases/tag/core-5.0.2))

- 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)
- 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)
- 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)

## Mapbox Navigation SDK 2.10.0-alpha.1 - 28 October, 2022
### Changelog
[Changes between v2.9.0 and v2.10.0-alpha.1](https://github.com/mapbox/mapbox-navigation-android/compare/v2.9.0...v2.10.0-alpha.1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
mapboxNavigation.onDestroy()
maneuverApi.cancel()
speechAPI.cancel()
speechAPI.destroy()
voiceInstructionsPlayer.shutdown()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ class MapboxVoiceActivity : AppCompatActivity(), OnMapLongClickListener {
mapboxReplayer.finish()
mapboxNavigation.onDestroy()
speechApi.cancel()
speechApi.destroy()
voiceInstructionsPlayer.shutdown()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import com.mapbox.navigation.core.directions.session.RoutesExtra
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.history.MapboxHistoryReader
import com.mapbox.navigation.core.history.MapboxHistoryRecorder
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
import com.mapbox.navigation.core.internal.ReachabilityService
import com.mapbox.navigation.core.internal.telemetry.CustomEvent
import com.mapbox.navigation.core.internal.telemetry.UserFeedbackCallback
Expand Down Expand Up @@ -405,7 +406,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
private set

init {
if (hasInstance) {
if (LegacyMapboxNavigationInstanceHolder.peek() != null) {
throw IllegalStateException(
"""
A different MapboxNavigation instance already exists.
Expand All @@ -414,7 +415,6 @@ class MapboxNavigation @VisibleForTesting internal constructor(
""".trimIndent()
)
}
hasInstance = true

val config = NavigatorLoader.createConfig(
navigationOptions.deviceProfile,
Expand Down Expand Up @@ -583,6 +583,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
navigator.cache
)
roadObjectMatcher = RoadObjectMatcher(navigator)
LegacyMapboxNavigationInstanceHolder.onCreated(this)
}

/**
Expand Down Expand Up @@ -1212,7 +1213,7 @@ class MapboxNavigation @VisibleForTesting internal constructor(
routesPreviewController.unregisterAllRoutesPreviewObservers()

isDestroyed = true
hasInstance = false
LegacyMapboxNavigationInstanceHolder.onDestroyed()
}

/**
Expand Down Expand Up @@ -2072,9 +2073,6 @@ class MapboxNavigation @VisibleForTesting internal constructor(

private companion object {

@Volatile
private var hasInstance = false

private const val LOG_CATEGORY = "MapboxNavigation"
private const val USER_AGENT: String = "MapboxNavigationNative"
private const val THREADS_COUNT = 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mapbox.navigation.core

import androidx.annotation.UiThread
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder

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

/**
* Create MapboxNavigation with provided options.
Expand All @@ -25,12 +24,8 @@ object MapboxNavigationProvider {
message = "Set the navigation options with MapboxNavigationApp.setup"
)
fun create(navigationOptions: NavigationOptions): MapboxNavigation {
mapboxNavigation?.onDestroy()
mapboxNavigation = MapboxNavigation(
navigationOptions
)

return mapboxNavigation!!
LegacyMapboxNavigationInstanceHolder.peek()?.onDestroy()
return MapboxNavigation(navigationOptions)
}

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

return mapboxNavigation!!
return LegacyMapboxNavigationInstanceHolder.peek()!!
}

/**
Expand All @@ -59,15 +54,14 @@ object MapboxNavigationProvider {
message = "MapboxNavigationApp will determine when to destroy MapboxNavigation instances"
)
fun destroy() {
mapboxNavigation?.onDestroy()
mapboxNavigation = null
LegacyMapboxNavigationInstanceHolder.peek()?.onDestroy()
}

/**
* Check if MapboxNavigation is created.
*/
@JvmStatic
fun isCreated(): Boolean {
return mapboxNavigation?.isDestroyed == false
return LegacyMapboxNavigationInstanceHolder.peek()?.isDestroyed == false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.mapbox.navigation.core.internal

import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.mapbox.navigation.core.MapboxNavigation
import java.util.concurrent.CopyOnWriteArraySet

fun interface MapboxNavigationCreateObserver {

fun onCreated(mapboxNavigation: MapboxNavigation)
}

@Deprecated("Used to keep track of MapboxNavigation instances created via deprecated methods")
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird, why are we adding a new @Deprecated object? 🤔

Copy link
Contributor Author

@dzinad dzinad Nov 23, 2022

Choose a reason for hiding this comment

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

I have to register RoutesObserver and RouteProgressObserver. And there are multiple ways of creating MapboxNavigation :

  1. constructor
  2. MapboxNavigationProvider (deprecated)
  3. MapboxNavigationApp
    The "correct" way of observing MapboxNavigation is using MapboxNavigationApp but the other ways are widely used and I want the feature to work for all the users. So LegacyMapboxNavigationInstanceHolder tracks all MapboxNavigation instances. Not sure if the naming is very good but I wanted to emphasize by this that the MapboxNavigationApp is the appropriate way to do it and LegacyMapboxNavigationInstanceHolder is just for us to support other creation methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

LegacyMapboxNavigationInstanceHolder is an internal object. User aren't supposed to use it anyway, are they?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They aren't. It's just a note for us. I can just add a comment or sth instead of deprecating it.
But actually I'm thinking that it should not be deprecated at all. We still expose a valid way of creating MapboxNavigation via constructor and some customers use it. So it's not really "legacy", it's more like "everything including legacy". I just don't like it that we have MapboxNavigationApp and this new way of observing. But since the new one is internal, it's not that big a deal.

Copy link
Contributor

Choose a reason for hiding this comment

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

LegacyMapboxNavigationInstanceHolder is definitely not an improvement to MapboxNavigationApp though, in my opinion.

MapboxNavigationCreateObserver is also redundant.. It is the same as MapboxNavigationObserver.onAttached.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I's not, see #6547 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

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

have to register RoutesObserver

This is not a reason to require an automatic link. It can be done after MapboxNavigation is constructed

@UiThread
object LegacyMapboxNavigationInstanceHolder {

@Volatile
private var mapboxNavigation: MapboxNavigation? = null

private val createObservers = CopyOnWriteArraySet<MapboxNavigationCreateObserver>()

fun onCreated(instance: MapboxNavigation) {
mapboxNavigation = instance
createObservers.forEach { it.onCreated(instance) }
}

fun onDestroyed() {
mapboxNavigation = null
}

fun peek(): MapboxNavigation? = mapboxNavigation

fun registerCreateObserver(observer: MapboxNavigationCreateObserver) {
createObservers.add(observer)
mapboxNavigation?.let { observer.onCreated(it) }
}

fun unregisterCreateObserver(observer: MapboxNavigationCreateObserver) {
createObservers.remove(observer)
}

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun unregisterAllCreateObservers() {
createObservers.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.mapbox.navigation.core

import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
import com.mapbox.navigation.core.internal.MapboxNavigationCreateObserver
import com.mapbox.navigation.testing.LoggingFrontendTestRule
import io.mockk.clearMocks
import io.mockk.mockk
import io.mockk.verify
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class LegacyMapboxNavigationInstanceHolderTest {

@get:Rule
val loggerRule = LoggingFrontendTestRule()
private val observer = mockk<MapboxNavigationCreateObserver>(relaxed = true)
private val mapboxNavigation = mockk<MapboxNavigation>(relaxed = true)

@Before
fun setUp() {
LegacyMapboxNavigationInstanceHolder.unregisterAllCreateObservers()
LegacyMapboxNavigationInstanceHolder.onDestroyed()
}

@After
fun tearDown() {
LegacyMapboxNavigationInstanceHolder.unregisterAllCreateObservers()
LegacyMapboxNavigationInstanceHolder.onDestroyed()
}

@Test
fun `observer is invoked on registration if has mapboxNavigation`() {
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)

LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)

verify(exactly = 1) { observer.onCreated(mapboxNavigation) }
}

@Test
fun `observer is not invoked on registration if has no mapboxNavigation`() {
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)

verify(exactly = 0) { observer.onCreated(mapboxNavigation) }
}

@Test
fun `observer is not invoked on registration if has destroyed mapboxNavigation`() {
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
LegacyMapboxNavigationInstanceHolder.onDestroyed()

LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)

verify(exactly = 0) { observer.onCreated(mapboxNavigation) }
}

@Test
fun `observer is invoked when mapboxNavigation is created`() {
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)

LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)

verify(exactly = 1) { observer.onCreated(mapboxNavigation) }
}

@Test
fun `observer is invoked when new mapboxNavigation is created`() {
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
clearMocks(observer, answers = false)

val newNavigation = mockk<MapboxNavigation>(relaxed = true)
LegacyMapboxNavigationInstanceHolder.onCreated(newNavigation)

verify(exactly = 1) { observer.onCreated(newNavigation) }
}

@Test
fun `removed observer is not invoked when mapboxNavigation is created`() {
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
LegacyMapboxNavigationInstanceHolder.unregisterCreateObserver(observer)

LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)

verify(exactly = 0) { observer.onCreated(any()) }
}

@Test
fun `multiple observers are invoked when new mapboxNavigation is created`() {
val secondObserver = mockk<MapboxNavigationCreateObserver>(relaxed = true)
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(observer)
LegacyMapboxNavigationInstanceHolder.registerCreateObserver(secondObserver)

LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)

verify(exactly = 1) {
observer.onCreated(mapboxNavigation)
secondObserver.onCreated(mapboxNavigation)
}
}

@Test
fun `peek with no instance`() {
assertNull(LegacyMapboxNavigationInstanceHolder.peek())
}

@Test
fun `peek with active instance`() {
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)

assertEquals(mapboxNavigation, LegacyMapboxNavigationInstanceHolder.peek())
}

@Test
fun `peek with destroyed instance`() {
LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation)
LegacyMapboxNavigationInstanceHolder.onDestroyed()

assertNull(LegacyMapboxNavigationInstanceHolder.peek())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.mapbox.navigation.core.accounts.BillingController
import com.mapbox.navigation.core.arrival.ArrivalProgressObserver
import com.mapbox.navigation.core.directions.session.DirectionsSession
import com.mapbox.navigation.core.ev.EVDynamicDataHolder
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
import com.mapbox.navigation.core.navigator.CacheHandleWrapper
import com.mapbox.navigation.core.preview.RoutesPreviewController
import com.mapbox.navigation.core.reroute.RerouteController
Expand Down Expand Up @@ -129,6 +130,7 @@ internal open class MapboxNavigationBaseTest {

@Before
open fun setUp() {
LegacyMapboxNavigationInstanceHolder.onDestroyed()
every { threadController.getMainScopeAndRootJob() } answers {
JobControl(mockk(), coroutineRule.createTestScope())
}
Expand Down Expand Up @@ -247,6 +249,7 @@ internal open class MapboxNavigationBaseTest {
unmockkObject(EventsServiceProvider)
unmockkObject(TelemetryServiceProvider)
unmockkObject(TelemetryUtilsDelegate)
LegacyMapboxNavigationInstanceHolder.onDestroyed()
}

fun createMapboxNavigation() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.mapbox.navigation.core.directions.session.RoutesExtra
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
import com.mapbox.navigation.core.internal.HistoryRecordingStateChangeObserver
import com.mapbox.navigation.core.internal.LegacyMapboxNavigationInstanceHolder
import com.mapbox.navigation.core.internal.extensions.registerHistoryRecordingStateChangeObserver
import com.mapbox.navigation.core.internal.extensions.unregisterHistoryRecordingStateChangeObserver
import com.mapbox.navigation.core.internal.telemetry.NavigationCustomEventType
Expand Down Expand Up @@ -56,6 +57,7 @@ import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot
import io.mockk.verify
import io.mockk.verifyOrder
Expand Down Expand Up @@ -1898,4 +1900,32 @@ internal class MapboxNavigationTest : MapboxNavigationBaseTest() {
routesPreviewController.previewNavigationRoutes(emptyList())
}
}

@Test
fun mapboxNavigationCreationSetsInstanceToHolder() {
mockkObject(LegacyMapboxNavigationInstanceHolder) {
createMapboxNavigation()
verify { LegacyMapboxNavigationInstanceHolder.onCreated(mapboxNavigation) }
}
}

@Test(expected = IllegalStateException::class)
fun mapboxNavigationIsNotCreatedIfHolderHasInstance() {
mockkObject(LegacyMapboxNavigationInstanceHolder) {
every { LegacyMapboxNavigationInstanceHolder.peek() } returns mockk(relaxed = true)
createMapboxNavigation()
verify(exactly = 0) {
LegacyMapboxNavigationInstanceHolder.onCreated(any())
}
}
Comment on lines +1914 to +1920
Copy link
Contributor

Choose a reason for hiding this comment

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

will it test the same?

Suggested change
mockkObject(LegacyMapboxNavigationInstanceHolder) {
every { LegacyMapboxNavigationInstanceHolder.peek() } returns mockk(relaxed = true)
createMapboxNavigation()
verify(exactly = 0) {
LegacyMapboxNavigationInstanceHolder.onCreated(any())
}
}
createMapboxNavigation()
createMapboxNavigation()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How? There is not verification that onCreated was not called the second time.

}

@Test
fun mapboxNavigationDestructionRemovesInstanceFromHolder() {
mockkObject(LegacyMapboxNavigationInstanceHolder) {
createMapboxNavigation()
mapboxNavigation.onDestroy()
verify { LegacyMapboxNavigationInstanceHolder.onDestroyed() }
}
Comment on lines +1925 to +1929
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think is more important for us:

  1. MapboxNavigation calls LegacyMapboxNavigationInstanceHolder.onDestroyed() during destroy
  2. User can create a new instance of MapboxNavigation when the old one is destroyed

?

Second statement seems more important to me, so I would test it instead

Suggested change
mockkObject(LegacyMapboxNavigationInstanceHolder) {
createMapboxNavigation()
mapboxNavigation.onDestroy()
verify { LegacyMapboxNavigationInstanceHolder.onDestroyed() }
}
createMapboxNavigation()
mapboxNavigation.onDestroy()
createMapboxNavigation()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your test would have passed even without the LegacyMapboxNavigationInstanceHolder...
What I want to test here is that LegacyMapboxNavigationInstanceHolder will be cleared. And what happens when it's cleared is tested in LegacyMapboxNavigationInstanceHolderTest.

}
}
Loading