Skip to content

Commit 53f8c17

Browse files
authored
Add MapboxCarMapLoader (#6530)
1 parent 9446e35 commit 53f8c17

7 files changed

Lines changed: 255 additions & 63 deletions

File tree

libnavui-androidauto/CHANGELOG.md

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

55
## Unreleased
66
#### Features
7+
- Added `MapboxCarMapLoader` to handle dark and light map style configuration changes. [#6530](https://github.com/mapbox/mapbox-navigation-android/pull/6530)
8+
79
#### Bug fixes and improvements
810
- Moved `surfacelayer` package into an internal package because it will be replaced by `BitmapWidget`. [#6527](https://github.com/mapbox/mapbox-navigation-android/pull/6527)
911
- Flattened the `car` package because it is adding an unnecessary and confusing layer. [#6527](https://github.com/mapbox/mapbox-navigation-android/pull/6527)

libnavui-androidauto/api/current.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,17 @@ package com.mapbox.androidauto.location {
249249

250250
}
251251

252+
package com.mapbox.androidauto.map {
253+
254+
public final class MapboxCarMapLoader implements com.mapbox.maps.extension.androidauto.MapboxCarMapObserver {
255+
ctor public MapboxCarMapLoader();
256+
method public com.mapbox.androidauto.map.MapboxCarMapLoader onCarConfigurationChanged(androidx.car.app.CarContext carContext);
257+
method public com.mapbox.androidauto.map.MapboxCarMapLoader setDarkStyleOverride(com.mapbox.maps.extension.style.StyleContract.StyleExtension? styleContract);
258+
method public com.mapbox.androidauto.map.MapboxCarMapLoader setLightStyleOverride(com.mapbox.maps.extension.style.StyleContract.StyleExtension? styleContract);
259+
}
260+
261+
}
262+
252263
package com.mapbox.androidauto.map.compass {
253264

254265
public final class CarCompassSurfaceRenderer implements com.mapbox.maps.extension.androidauto.MapboxCarMapObserver {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.mapbox.androidauto.map
2+
3+
import androidx.car.app.CarContext
4+
import androidx.car.app.Session
5+
import com.mapbox.androidauto.internal.logAndroidAuto
6+
import com.mapbox.androidauto.internal.logAndroidAutoFailure
7+
import com.mapbox.maps.MapboxExperimental
8+
import com.mapbox.maps.MapboxMap
9+
import com.mapbox.maps.extension.androidauto.MapboxCarMap
10+
import com.mapbox.maps.extension.androidauto.MapboxCarMapObserver
11+
import com.mapbox.maps.extension.androidauto.MapboxCarMapSurface
12+
import com.mapbox.maps.extension.observable.eventdata.MapLoadingErrorEventData
13+
import com.mapbox.maps.extension.style.StyleContract
14+
import com.mapbox.maps.extension.style.style
15+
import com.mapbox.maps.plugin.delegates.listeners.OnMapLoadErrorListener
16+
import com.mapbox.navigation.ui.maps.NavigationStyles
17+
18+
/**
19+
* Default map style loader that is designed for Android Auto. This map loader assumes you want to
20+
* keep map styles in sync with the [CarContext.isDarkMode].
21+
*
22+
* To use, register an instance to [MapboxCarMap.registerObserver]. It will automatically load the
23+
* map for as long as it is registered. To disable you can unregister it with
24+
* [MapboxCarMap.unregisterObserver]. Override the [Session.onCarConfigurationChanged] and call the
25+
* [onCarConfigurationChanged].
26+
*/
27+
@OptIn(MapboxExperimental::class)
28+
class MapboxCarMapLoader : MapboxCarMapObserver {
29+
30+
private var mapboxMap: MapboxMap? = null
31+
private var lightStyleOverride: StyleContract.StyleExtension? = null
32+
private var darkStyleOverride: StyleContract.StyleExtension? = null
33+
34+
private val logMapError = object : OnMapLoadErrorListener {
35+
override fun onMapLoadError(eventData: MapLoadingErrorEventData) {
36+
val errorData = "${eventData.type} ${eventData.message}"
37+
logAndroidAutoFailure("onMapLoadError $errorData")
38+
}
39+
}
40+
41+
override fun onAttached(mapboxCarMapSurface: MapboxCarMapSurface) {
42+
mapboxMap = mapboxCarMapSurface.mapSurface.getMapboxMap()
43+
with(mapboxCarMapSurface) {
44+
logAndroidAuto("onAttached load style")
45+
mapSurface.getMapboxMap().loadStyle(
46+
mapStyle(carContext.isDarkMode),
47+
onStyleLoaded = {
48+
logAndroidAuto("onAttached style loaded")
49+
},
50+
onMapLoadErrorListener = logMapError
51+
)
52+
}
53+
}
54+
55+
override fun onDetached(mapboxCarMapSurface: MapboxCarMapSurface) {
56+
mapboxMap = null
57+
}
58+
59+
/**
60+
* Set the map style to apply when [CarContext.isDarkMode] is false. Setting to `null` will use
61+
* a default map style.
62+
*
63+
* @see onCarConfigurationChanged to apply changes after changing.
64+
*/
65+
fun setLightStyleOverride(styleContract: StyleContract.StyleExtension?) = apply {
66+
this.lightStyleOverride = styleContract
67+
}
68+
69+
/**
70+
* Set the map style to apply when [CarContext.isDarkMode] is true. Setting to `null` will use
71+
* a default map style.
72+
*
73+
* @see onCarConfigurationChanged to apply changes after changing.
74+
*/
75+
fun setDarkStyleOverride(styleContract: StyleContract.StyleExtension?) = apply {
76+
this.darkStyleOverride = styleContract
77+
}
78+
79+
/**
80+
* This will use [CarContext.isDarkMode] to determine if the dark or light style should be
81+
* loaded. If this is called while the map is detached, there is no operation.
82+
*
83+
* @see setLightStyleOverride
84+
* @see setDarkStyleOverride
85+
*
86+
* @param carContext forwarded from [Session.onCarConfigurationChanged]
87+
*/
88+
fun onCarConfigurationChanged(carContext: CarContext) = apply {
89+
mapboxMap?.loadStyle(
90+
mapStyle(carContext.isDarkMode),
91+
onStyleLoaded = { style ->
92+
logAndroidAuto("updateMapStyle styleAvailable ${style.styleURI}")
93+
},
94+
onMapLoadErrorListener = logMapError
95+
) ?: logAndroidAuto(
96+
"onCarConfigurationChanged did not load the map because the map is not attached"
97+
)
98+
}
99+
100+
private fun mapStyle(isDarkMode: Boolean): StyleContract.StyleExtension {
101+
return if (isDarkMode) {
102+
darkStyleOverride ?: DEFAULT_NIGHT_STYLE
103+
} else {
104+
lightStyleOverride ?: DEFAULT_DAY_STYLE
105+
}
106+
}
107+
108+
private companion object {
109+
private val DEFAULT_DAY_STYLE = style(NavigationStyles.NAVIGATION_DAY_STYLE) { }
110+
private val DEFAULT_NIGHT_STYLE = style(NavigationStyles.NAVIGATION_NIGHT_STYLE) { }
111+
}
112+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.mapbox.androidauto.map
2+
3+
import com.mapbox.maps.MapboxExperimental
4+
import com.mapbox.maps.Style
5+
import com.mapbox.maps.extension.androidauto.MapboxCarMapSurface
6+
import com.mapbox.maps.extension.style.StyleContract
7+
import com.mapbox.navigation.testing.LoggingFrontendTestRule
8+
import com.mapbox.navigation.ui.maps.NavigationStyles
9+
import io.mockk.CapturingSlot
10+
import io.mockk.every
11+
import io.mockk.mockk
12+
import io.mockk.slot
13+
import org.junit.Assert.assertEquals
14+
import org.junit.Rule
15+
import org.junit.Test
16+
17+
@OptIn(MapboxExperimental::class)
18+
class MapboxCarMapLoaderTest {
19+
20+
@get:Rule
21+
val loggerRule = LoggingFrontendTestRule()
22+
23+
private val sut = MapboxCarMapLoader()
24+
25+
@Test
26+
fun `functions can be called while map is detached`() {
27+
sut.setLightStyleOverride(mockk())
28+
sut.setDarkStyleOverride(mockk())
29+
sut.onCarConfigurationChanged(mockk())
30+
}
31+
32+
@Test
33+
fun `onAttached will load the day map style when isDarkMode is false`() {
34+
val styleExtensionSlot = slot<StyleContract.StyleExtension>()
35+
val mapSurface: MapboxCarMapSurface = mockMapboxCarMapSurface(styleExtensionSlot)
36+
every { mapSurface.carContext } returns mockk {
37+
every { isDarkMode } returns false
38+
}
39+
40+
sut.onAttached(mapSurface)
41+
42+
assertEquals(NavigationStyles.NAVIGATION_DAY_STYLE, styleExtensionSlot.captured.styleUri)
43+
}
44+
45+
@Test
46+
fun `onAttached will load the night map style when isDarkMode is true`() {
47+
val styleExtensionSlot = slot<StyleContract.StyleExtension>()
48+
val mapSurface: MapboxCarMapSurface = mockMapboxCarMapSurface(styleExtensionSlot)
49+
every { mapSurface.carContext } returns mockk {
50+
every { isDarkMode } returns true
51+
}
52+
53+
sut.onAttached(mapSurface)
54+
55+
assertEquals(NavigationStyles.NAVIGATION_NIGHT_STYLE, styleExtensionSlot.captured.styleUri)
56+
}
57+
58+
@Test
59+
fun `onAttached will load the light style override when isDarkMode is false`() {
60+
val styleExtensionSlot = slot<StyleContract.StyleExtension>()
61+
val mapSurface: MapboxCarMapSurface = mockMapboxCarMapSurface(styleExtensionSlot)
62+
every { mapSurface.carContext } returns mockk {
63+
every { isDarkMode } returns false
64+
}
65+
val darkOverride: StyleContract.StyleExtension = mockk {
66+
every { styleUri } returns "test-light-override"
67+
}
68+
69+
sut.setLightStyleOverride(darkOverride).onAttached(mapSurface)
70+
71+
assertEquals("test-light-override", styleExtensionSlot.captured.styleUri)
72+
}
73+
74+
@Test
75+
fun `onAttached will load the dark style override when isDarkMode is true`() {
76+
val styleExtensionSlot = slot<StyleContract.StyleExtension>()
77+
val mapSurface: MapboxCarMapSurface = mockMapboxCarMapSurface(styleExtensionSlot)
78+
every { mapSurface.carContext } returns mockk {
79+
every { isDarkMode } returns true
80+
}
81+
val darkOverride: StyleContract.StyleExtension = mockk {
82+
every { styleUri } returns "test-dark-override"
83+
}
84+
85+
sut.setDarkStyleOverride(darkOverride).onAttached(mapSurface)
86+
87+
assertEquals("test-dark-override", styleExtensionSlot.captured.styleUri)
88+
}
89+
90+
private fun mockMapboxCarMapSurface(
91+
styleExtensionSlot: CapturingSlot<StyleContract.StyleExtension>
92+
): MapboxCarMapSurface {
93+
return mockk {
94+
every { mapSurface } returns mockk {
95+
every { getMapboxMap() } returns mockk {
96+
every {
97+
loadStyle(
98+
capture(styleExtensionSlot),
99+
any<Style.OnStyleLoaded>(),
100+
any()
101+
)
102+
} answers {
103+
secondArg<Style.OnStyleLoaded>().onStyleLoaded(mockk())
104+
}
105+
}
106+
}
107+
}
108+
}
109+
}

qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/car/MainCarMapLoader.kt

Lines changed: 0 additions & 61 deletions
This file was deleted.

qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/car/MainCarSession.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.mapbox.android.core.permissions.PermissionsManager
1414
import com.mapbox.androidauto.MapboxCarContext
1515
import com.mapbox.androidauto.deeplink.GeoDeeplinkNavigateAction
1616
import com.mapbox.androidauto.internal.logAndroidAuto
17+
import com.mapbox.androidauto.map.MapboxCarMapLoader
1718
import com.mapbox.androidauto.map.compass.CarCompassSurfaceRenderer
1819
import com.mapbox.androidauto.map.logo.CarLogoSurfaceRenderer
1920
import com.mapbox.androidauto.notification.MapboxCarNotificationOptions
@@ -33,7 +34,7 @@ import kotlinx.coroutines.launch
3334
@OptIn(MapboxExperimental::class)
3435
class MainCarSession : Session() {
3536

36-
private val carMapLoader = MainCarMapLoader()
37+
private val carMapLoader = MapboxCarMapLoader()
3738
private val mapboxCarMap = MapboxCarMap()
3839
.registerObserver(carMapLoader)
3940
private val mapboxCarContext = MapboxCarContext(lifecycle, mapboxCarMap)
@@ -116,7 +117,7 @@ class MainCarSession : Session() {
116117
override fun onCarConfigurationChanged(newConfiguration: Configuration) {
117118
logAndroidAuto("onCarConfigurationChanged ${carContext.isDarkMode}")
118119

119-
carMapLoader.updateMapStyle(carContext.isDarkMode)
120+
carMapLoader.onCarConfigurationChanged(carContext)
120121
}
121122

122123
override fun onNewIntent(intent: Intent) {

qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/testing/JavaInterfaceChecker.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package com.mapbox.navigation.qa_test_app.testing;
22

3+
import static com.mapbox.maps.extension.style.StyleExtensionImplKt.style;
4+
5+
import com.mapbox.androidauto.map.MapboxCarMapLoader;
6+
import com.mapbox.maps.extension.style.StyleContract;
7+
import com.mapbox.maps.extension.style.StyleExtensionImpl;
38
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp;
9+
import com.mapbox.navigation.ui.maps.NavigationStyles;
410
import com.mapbox.navigation.ui.voice.api.MapboxAudioGuidance;
511

12+
import kotlin.Unit;
13+
import kotlin.jvm.functions.Function1;
614
import kotlinx.coroutines.CoroutineScope;
715

816
class JavaInterfaceChecker {
@@ -20,4 +28,14 @@ void MapboxAudioGuidance(
2028
// observe state
2129
});
2230
}
31+
32+
void MapboxCarMapLoader() {
33+
MapboxCarMapLoader sut = new MapboxCarMapLoader();
34+
sut.setDarkStyleOverride(styleExtension(NavigationStyles.NAVIGATION_NIGHT_STYLE));
35+
sut.setLightStyleOverride(styleExtension(NavigationStyles.NAVIGATION_DAY_STYLE));
36+
}
37+
38+
StyleContract.StyleExtension styleExtension(String styleUri) {
39+
return style(styleUri, builder -> Unit.INSTANCE);
40+
}
2341
}

0 commit comments

Comments
 (0)