Skip to content

Commit 3a5c18c

Browse files
committed
feat: support prompt visibility handling on Android Auto and CarPlay views
1 parent c165694 commit 3a5c18c

31 files changed

+3141
-417
lines changed

android/src/main/kotlin/com/google/maps/flutter/navigation/AndroidAutoBaseScreen.kt

Lines changed: 158 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,59 @@ import androidx.lifecycle.LifecycleOwner
3535
import com.google.android.gms.maps.CameraUpdateFactory
3636
import com.google.android.gms.maps.GoogleMap
3737
import com.google.android.gms.maps.GoogleMapOptions
38-
import com.google.android.libraries.navigation.NavigationViewForAuto
38+
import com.google.android.libraries.navigation.NavigationView
39+
import com.google.android.libraries.navigation.PromptVisibilityChangedListener
3940

4041
open class AndroidAutoBaseScreen(carContext: CarContext) :
4142
Screen(carContext), SurfaceCallback, NavigationReadyListener {
43+
44+
companion object {
45+
/**
46+
* Map options to use for Android Auto views. Can be set before the Android Auto screen is
47+
* created to customize map appearance.
48+
*/
49+
var mapOptions: AutoMapViewOptions? = null
50+
}
51+
52+
/**
53+
* Provides the map options to use when creating the Android Auto map view.
54+
*
55+
* Override this method in your AndroidAutoBaseScreen subclass to provide custom map options from
56+
* the native layer. This is useful when you want to set map configuration (like mapId) directly
57+
* in native code instead of from Flutter, especially when the Android Auto screen may already be
58+
* open.
59+
*
60+
* The default implementation returns the value from the companion object, which can be set from
61+
* Flutter via GoogleMapsAutoViewController.setAutoMapOptions().
62+
*
63+
* @return AutoMapViewOptions containing map configuration, or null to use defaults
64+
*
65+
* Example:
66+
* ```kotlin
67+
* override fun getAutoMapOptions(): AutoMapViewOptions? {
68+
* return AutoMapViewOptions(
69+
* mapId = "your-map-id",
70+
* mapType = GoogleMap.MAP_TYPE_SATELLITE,
71+
* mapColorScheme = UIUserInterfaceStyle.DARK,
72+
* forceNightMode = NavigationView.FORCE_NIGHT_MODE_AUTO
73+
* )
74+
* }
75+
* ```
76+
*/
77+
public open fun getAutoMapOptions(): AutoMapViewOptions? {
78+
return mapOptions
79+
}
80+
4281
private val VIRTUAL_DISPLAY_NAME = "AndroidAutoNavScreen"
4382
private var mVirtualDisplay: VirtualDisplay? = null
4483
private var mPresentation: Presentation? = null
45-
private var mNavigationView: NavigationViewForAuto? = null
84+
private var mNavigationView: NavigationView? = null
4685
private var mAutoMapView: GoogleMapsAutoMapView? = null
4786
private var mViewRegistry: GoogleMapsViewRegistry? = null
87+
private var mPromptVisibilityListener: PromptVisibilityChangedListener? = null
4888
protected var mIsNavigationReady: Boolean = false
4989
var mGoogleMap: GoogleMap? = null
90+
private var mIsPromptVisible: Boolean = false
5091

5192
init {
5293
initializeSurfaceCallback()
@@ -101,11 +142,43 @@ open class AndroidAutoBaseScreen(carContext: CarContext) :
101142
mPresentation = Presentation(carContext, virtualDisplay.display)
102143
val presentation = mPresentation ?: return
103144

104-
mNavigationView = NavigationViewForAuto(carContext)
145+
// Get map options from overridable method (can be customized in subclasses)
146+
val autoMapOptions = getAutoMapOptions()
147+
val googleMapOptions =
148+
GoogleMapOptions().apply {
149+
compassEnabled(false) // Always disable compass for Android Auto
150+
151+
// Apply custom map ID if provided
152+
autoMapOptions?.mapId?.let { mapId -> mapId(mapId) }
153+
154+
// Apply map type if provided
155+
autoMapOptions?.mapType?.let { type -> mapType(type) }
156+
157+
// Apply map color scheme if provided
158+
autoMapOptions?.mapColorScheme?.let { colorScheme -> mapColorScheme(colorScheme) }
159+
}
160+
161+
// Create NavigationView with the configured options
162+
mNavigationView = NavigationView(carContext, googleMapOptions)
105163
val navigationView = mNavigationView ?: return
106-
navigationView.onCreate(null)
107-
navigationView.onStart()
108-
navigationView.onResume()
164+
165+
// Apply force night mode if provided (separate from color scheme)
166+
autoMapOptions?.forceNightMode?.let { forceNightMode ->
167+
navigationView.setForceNightMode(forceNightMode)
168+
}
169+
170+
// Configure NavigationView for Android Auto
171+
navigationView.apply {
172+
onCreate(null)
173+
onStart()
174+
onResume()
175+
setHeaderEnabled(false)
176+
setRecenterButtonEnabled(false)
177+
setEtaCardEnabled(false)
178+
setSpeedometerEnabled(false)
179+
setTripProgressBarEnabled(false)
180+
setReportIncidentButtonEnabled(false)
181+
}
109182

110183
presentation.setContentView(navigationView)
111184
presentation.show()
@@ -116,14 +189,24 @@ open class AndroidAutoBaseScreen(carContext: CarContext) :
116189
if (viewRegistry != null && imageRegistry != null) {
117190
mGoogleMap = googleMap
118191
mViewRegistry = viewRegistry
192+
119193
mAutoMapView =
120194
GoogleMapsAutoMapView(
121195
MapOptions(GoogleMapOptions(), null),
122196
viewRegistry,
123197
imageRegistry,
124198
navigationView,
199+
navigationView,
125200
googleMap,
126201
)
202+
203+
// Set up prompt visibility listener with direct access to NavigationView
204+
mPromptVisibilityListener = PromptVisibilityChangedListener { promptVisible ->
205+
mIsPromptVisible = promptVisible
206+
onPromptVisibilityChanged(promptVisible)
207+
}
208+
navigationView.addPromptVisibilityChangedListener(mPromptVisibilityListener)
209+
127210
sendAutoScreenAvailabilityChangedEvent(true)
128211
invalidate()
129212
}
@@ -133,6 +216,14 @@ open class AndroidAutoBaseScreen(carContext: CarContext) :
133216
override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
134217
super.onSurfaceDestroyed(surfaceContainer)
135218
sendAutoScreenAvailabilityChangedEvent(false)
219+
220+
// Clean up prompt visibility listener
221+
if (mPromptVisibilityListener != null && mNavigationView != null) {
222+
mNavigationView?.removePromptVisibilityChangedListener(mPromptVisibilityListener)
223+
mPromptVisibilityListener = null
224+
}
225+
mIsPromptVisible = false
226+
136227
mViewRegistry?.unregisterAndroidAutoView()
137228
mNavigationView?.onPause()
138229
mNavigationView?.onStop()
@@ -166,6 +257,14 @@ open class AndroidAutoBaseScreen(carContext: CarContext) :
166257
) {}
167258
}
168259

260+
// Called when Flutter sends a custom event to native via sendCustomNavigationAutoEvent
261+
// Override this method in your AndroidAutoBaseScreen subclass to handle custom events from
262+
// Flutter
263+
open fun onCustomNavigationAutoEventFromFlutter(event: String, data: Any) {
264+
// Default implementation does nothing
265+
// Subclasses can override to handle custom events
266+
}
267+
169268
private fun sendAutoScreenAvailabilityChangedEvent(isAvailable: Boolean) {
170269
GoogleMapsNavigationPlugin.getInstance()?.autoViewEventApi?.onAutoScreenAvailabilityChanged(
171270
isAvailable
@@ -175,4 +274,57 @@ open class AndroidAutoBaseScreen(carContext: CarContext) :
175274
override fun onNavigationReady(ready: Boolean) {
176275
mIsNavigationReady = ready
177276
}
277+
278+
/**
279+
* Checks if a traffic prompt is currently visible on the Android Auto screen.
280+
*
281+
* This can be useful to dynamically adjust your UI based on prompt visibility, such as when
282+
* building templates or deciding whether to show custom elements.
283+
*
284+
* @return true if a prompt is currently visible, false otherwise
285+
*
286+
* Example:
287+
* ```kotlin
288+
* override fun onGetTemplate(): Template {
289+
* val builder = NavigationTemplate.Builder()
290+
*
291+
* // Only show custom actions if prompt is not visible
292+
* if (!isPromptVisible()) {
293+
* builder.setActionStrip(myCustomActionStrip)
294+
* }
295+
*
296+
* return builder.build()
297+
* }
298+
* ```
299+
*/
300+
fun isPromptVisible(): Boolean {
301+
return mIsPromptVisible
302+
}
303+
304+
/**
305+
* Called when traffic prompt visibility changes on the Android Auto screen.
306+
*
307+
* Override this method to add custom behavior when prompts appear or disappear, such as
308+
* hiding/showing your custom UI elements to avoid overlapping with system prompts.
309+
*
310+
* @param promptVisible true if the prompt is now visible, false if it's hidden
311+
*
312+
* Example:
313+
* ```kotlin
314+
* override fun onPromptVisibilityChanged(promptVisible: Boolean) {
315+
* super.onPromptVisibilityChanged(promptVisible)
316+
* if (promptVisible) {
317+
* // Hide your custom buttons or UI elements
318+
* } else {
319+
* // Show your custom buttons or UI elements
320+
* }
321+
* }
322+
* ```
323+
*/
324+
open fun onPromptVisibilityChanged(promptVisible: Boolean) {
325+
// Send event to Flutter by default
326+
GoogleMapsNavigationPlugin.getInstance()?.autoViewEventApi?.onPromptVisibilityChanged(
327+
promptVisible
328+
) {}
329+
}
178330
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.maps.flutter.navigation
18+
19+
/**
20+
* Options for configuring Android Auto map views. Contains only settings relevant for Android Auto
21+
* views.
22+
*/
23+
public data class AutoMapViewOptions(
24+
/** The initial camera position for the map view. */
25+
val cameraPosition: CameraPositionDto? = null,
26+
27+
/** Cloud-based map ID for custom styling. */
28+
val mapId: String? = null,
29+
30+
/** The type of map to display. */
31+
val mapType: Int? = null,
32+
33+
/** The color scheme for the map. */
34+
val mapColorScheme: Int? = null,
35+
36+
/** Forces night mode regardless of system settings. */
37+
val forceNightMode: Int? = null,
38+
)

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,26 @@
1717
package com.google.maps.flutter.navigation
1818

1919
import android.view.View
20+
import android.view.ViewGroup
2021
import com.google.android.gms.maps.GoogleMap
21-
import com.google.android.libraries.navigation.NavigationViewForAuto
22+
import com.google.android.libraries.navigation.NavigationView
2223

2324
class GoogleMapsAutoMapView
2425
internal constructor(
2526
mapOptions: MapOptions,
2627
viewRegistry: GoogleMapsViewRegistry,
2728
imageRegistry: ImageRegistry,
28-
private val mapView: NavigationViewForAuto,
29+
private val navigationView: NavigationView,
30+
private val viewGroup: ViewGroup,
2931
map: GoogleMap,
3032
) : GoogleMapsBaseMapView(null, mapOptions, null, imageRegistry) {
33+
private var _isTrafficPromptsEnabled: Boolean = true
34+
private var _isTrafficIncidentCardsEnabled: Boolean = true
35+
private var _isReportIncidentButtonEnabled: Boolean = true
36+
private var _forceNightMode: Int = 0
37+
3138
override fun getView(): View {
32-
return mapView
39+
return viewGroup
3340
}
3441

3542
init {
@@ -40,6 +47,42 @@ internal constructor(
4047
mapReady()
4148
}
4249

50+
override fun setTrafficPromptsEnabled(enabled: Boolean) {
51+
navigationView.setTrafficPromptsEnabled(enabled)
52+
_isTrafficPromptsEnabled = enabled
53+
}
54+
55+
override fun isTrafficPromptsEnabled(): Boolean {
56+
return _isTrafficPromptsEnabled
57+
}
58+
59+
override fun setTrafficIncidentCardsEnabled(enabled: Boolean) {
60+
navigationView.setTrafficIncidentCardsEnabled(enabled)
61+
_isTrafficIncidentCardsEnabled = enabled
62+
}
63+
64+
override fun isTrafficIncidentCardsEnabled(): Boolean {
65+
return _isTrafficIncidentCardsEnabled
66+
}
67+
68+
override fun setReportIncidentButtonEnabled(enabled: Boolean) {
69+
navigationView.setReportIncidentButtonEnabled(enabled)
70+
_isReportIncidentButtonEnabled = enabled
71+
}
72+
73+
override fun isReportIncidentButtonEnabled(): Boolean {
74+
return _isReportIncidentButtonEnabled
75+
}
76+
77+
override fun getForceNightMode(): Int {
78+
return _forceNightMode
79+
}
80+
81+
override fun setForceNightMode(forceNightMode: Int) {
82+
navigationView.setForceNightMode(forceNightMode)
83+
_forceNightMode = forceNightMode
84+
}
85+
4386
// Handled by AndroidAutoBaseScreen.
4487
override fun onStart(): Boolean {
4588
return super.onStart()

0 commit comments

Comments
 (0)