Esta página detalha os diferentes recursos da biblioteca Car App que podem ser usados para implementar a funcionalidade do seu app de navegação guiada.
Declarar suporte à navegação no manifesto
Seu app de navegação precisa declarar a
categoria de app para carros androidx.car.app.category.NAVIGATION
no filtro de
intent do CarAppService
:
<application>
...
<service
...
android:name=".MyNavigationCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.NAVIGATION"/>
</intent-filter>
</service>
...
</application>
Suporte a intents de navegação
Para oferecer suporte a intents de navegação no seu app, incluindo as que vêm do
Google Assistente com consultas por voz, o app precisa processar a
intent CarContext.ACTION_NAVIGATE
nos métodos
Session.onCreateScreen
e
Session.onNewIntent
.
Acesse a documentação de
CarContext.startCarApp
para consultar detalhes sobre o formato da intent.
Acessar os modelos de navegação
Os apps de navegação podem acessar os seguintes modelos, que exibem uma superfície em plano de fundo com o mapa e, durante a navegação ativa, navegação guiada direções
NavigationTemplate
: também mostra uma mensagem informativa opcional e estimativas da viagem durante a navegação ativa.MapWithContentTemplate
: Um modelo que permite que um aplicativo renderize blocos de mapa com algum tipo de conteúdo (por exemplo, uma lista). O conteúdo geralmente é renderizado como uma sobreposição sobre o Blocos de mapa, com as áreas estáveis e visíveis se ajustando ao conteúdo.
Para mais detalhes sobre como projetar a interface do usuário do seu app de navegação usando esses modelos, consulte Apps de navegação.
Para ter acesso aos modelos de navegação, seu app precisa declarar
a permissão androidx.car.app.NAVIGATION_TEMPLATES
no
arquivo AndroidManifest.xml
:
<manifest ...>
...
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>
...
</manifest>
Uma permissão adicional é necessária para desenhar mapas.
Migrar para o MapWithContentTemplate
A partir do nível 7 da API Car App, a
MapTemplate
,
PlaceListNavigationTemplate
,
e RoutePreviewNavigationTemplate
foram descontinuadas. Os modelos descontinuados vão continuar a ter suporte, mas
é altamente recomendável migrar para MapWithContentTemplate
.
A funcionalidade fornecida por esses modelos pode ser implementada
usando o MapWithContentTemplate
. Confira exemplos nos snippets a seguir:
Modelo de mapa
Kotlin
// MapTemplate (deprecated) val template = MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(mapController) .build()
Java
// MapTemplate (deprecated) MapTemplate template = new MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) build()) .setActionStrip(actionStrip) .setMapController(mapController) .build();
PlaceListNavigationTemplate
Kotlin
// PlaceListNavigationTemplate (deprecated) val template = PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// PlaceListNavigationTemplate (deprecated) PlaceListNavigationTemplate template = new PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
RoutePreviewNavigationTemplate
Kotlin
// RoutePreviewNavigationTemplate (deprecated) val template = RoutePreviewNavigationTemplate.Builder() .setItemList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .build()) .build()) .setHeader(header) .setNavigateAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .addAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .build()) .build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// RoutePreviewNavigationTemplate (deprecated) RoutePreviewNavigationTemplate template = new RoutePreviewNavigationTemplate.Builder() .setItemList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .build()) .build()) .setHeader(header) .setNavigateAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .addAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .build()) .build())) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
Informar metadados de navegação
Os apps de navegação precisam comunicar outros metadados ao host, que usa as informações para fornecer dados à unidade principal do veículo e evitar que esses apps entrem em conflito com os recursos compartilhados.
Os metadados de navegação são fornecidos pelo
serviço de carro NavigationManager
,
que pode ser acessado em
CarContext
:
Kotlin
val navigationManager = carContext.getCarService(NavigationManager::class.java)
Java
NavigationManager navigationManager = carContext.getCarService(NavigationManager.class);
Iniciar, encerrar e interromper a navegação
Para que o host gerencie vários apps de navegação, notificações de trajeto
e dados de cluster do veículo, ele precisa estar ciente do estado de
navegação atual. Quando um usuário iniciar a navegação, chame
NavigationManager.navigationStarted
.
Da mesma forma, quando a navegação terminar, por exemplo, ao chegar no
destino ou cancelar a navegação, chame
NavigationManager.navigationEnded
.
Chame NavigationManager.navigationEnded
apenas quando o usuário terminar a navegação. Por exemplo, se você precisar recalcular
o trajeto no meio de uma viagem,
use
Trip.Builder.setLoading(true)
.
Ocasionalmente, o host precisa que um app interrompa a navegação e chame
onStopNavigation
em um objeto
NavigationManagerCallback
fornecido pelo seu app usando o
NavigationManager.setNavigationManagerCallback
.
Para isso, o app precisará parar de emitir informações da próxima curva na exibição do cluster,
nas notificações de navegação e na orientação por voz.
Atualizar as informações da viagem
Durante a navegação ativa, chame
NavigationManager.updateTrip
.
As informações fornecidas nessa chamada podem ser usadas pelo cluster do veículo e
pelos avisos na tela. Dependendo do veículo específico que está sendo dirigido, nem todas
as informações são mostradas para o usuário.
Por exemplo, a unidade principal da área de trabalho (DHU, na sigla em inglês)
mostra a Step
adicionada à
Trip
, mas não mostra
a
informação de
Destination
.
Como desenhar na tela do cluster
Para oferecer uma experiência do usuário mais imersiva, vá além de mostrar metadados básicos na tela do cluster do veículo. A partir do nível 6 da API Car App, os apps de navegação têm a opção de renderizar o conteúdo diretamente na tela do cluster (em veículos com suporte), com as seguintes limitações:
- A API de tela do cluster não oferece suporte a controles de entrada.
- A tela do cluster só mostra blocos de mapas. Uma navegação de rota ativa pode ser exibida nos blocos, se desejar.
- A API de tela do cluster só oferece suporte ao
NavigationTemplate
.- Ao contrário das telas principais, as telas do cluster podem não mostrar de maneira consistente todos
os elementos da interface
NavigationTemplate
, como instruções passo a passo, cartões HEC e ações. Os blocos de mapas são o único elemento de interface mostrado de forma consistente.
- Ao contrário das telas principais, as telas do cluster podem não mostrar de maneira consistente todos
os elementos da interface
Declarar suporte a cluster
Para informar ao aplicativo hospedado que seu app oferece suporte à renderização
em telas de clusters, adicione um elemento androidx.car.app.category.FEATURE_CLUSTER
<category>
ao <intent-filter>
do CarAppService
, conforme mostrado no
snippet a seguir:
<application> ... <service ... android:name=".MyNavigationCarAppService" android:exported="true"> <intent-filter> <action android:name="androidx.car.app.CarAppService" /> <category android:name="androidx.car.app.category.NAVIGATION"/> <category android:name="androidx.car.app.category.FEATURE_CLUSTER"/> </intent-filter> </service> ... </application>
Ciclo de vida e gerenciamento do estado
A partir do nível 6 da API, o
fluxo de ciclo de vida do app para carro
permanece o mesmo, mas agora CarAppService::onCreateSession
aceita um parâmetro do
tipo SessionInfo
que oferece
mais informações sobre a Session
que está sendo criada, (ou seja, o tipo de tela
e o conjunto de modelos aceitos).
Os apps têm a opção de usar a mesma classe Session
para lidar com o
cluster e a tela principal, ou de criar Sessions
específicas para personalizar
o comportamento em cada tela, conforme mostrado no snippet a seguir.
Kotlin
override fun onCreateSession(sessionInfo: SessionInfo): Session { return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) { ClusterSession() } else { MainDisplaySession() } }
Java
@Override @NonNull public Session onCreateSession(@NonNull SessionInfo sessionInfo) { if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_CLUSTER) { return new ClusterSession(); } else { return new MainDisplaySession(); } }
Não há garantias sobre quando ou se a tela do cluster é fornecida.
Também é possível que a Session
do cluster seja a única Session
(por
exemplo, o usuário trocou a tela principal por outro app enquanto seu app
está navegando). O acordo "padrão" é de que o app assume o controle da
tela do cluster somente depois que NavigationManager::navigationStarted
é
chamado. No entanto, é possível que o app receba a tela do cluster
sem que haja nenhuma navegação ativa ou que nunca a
receba. Cabe ao seu app lidar com esses cenários renderizando o
estado inativo dos blocos de mapas.
O host cria instâncias separadas de binders e CarContext
por Session
. Isso
significa que, ao usar métodos como ScreenManager::push
ou
Screen::invalidate
, apenas a Session
na qual eles são chamados é
afetada. Os apps precisam criar os próprios canais de comunicação entre essas
instâncias se a comunicação entre cada Session
for necessária (por exemplo, usando
transmissões, um Singleton compartilhado ou algo
assim).
Como testar o suporte a clusters
Você pode testar a implementação no Android Auto e no Android Automotive OS. No Android Auto, isso é feito configurando a unidade principal do computador para emular uma tela de cluster secundária. No Android Automotive OS, as imagens genéricas do sistema para o nível 30 da API e versões mais recentes emulam uma tela de cluster.
Personalizar a TravelEstimate com texto ou ícone
Para personalizar a estimativa de viagem com texto, ícone ou ambos, use os métodos
setTripIcon
ou
setTripText
da classe
TravelEstimate.Builder
. O
NavigationTemplate
usa a
TravelEstimate
para definir texto e ícones opcionais ao lado ou no lugar dos dados de horário previsto de chegada, tempo restante e distância restante.
O snippet a seguir usa setTripIcon
e setTripText
para personalizar a
estimativa de viagem:
Kotlin
TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build()
Java
new TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build();
Fornecer notificações de navegação guiada
Dê instruções de navegação guiada usando uma notificação atualizada com frequência. Para ser tratado como uma notificação de navegação na tela do carro, o builder da notificação precisa fazer o seguinte:
- Marcar a notificação como em andamento com o
método
NotificationCompat.Builder.setOngoing
. - Definir a categoria da notificação como
Notification.CATEGORY_NAVIGATION
. - Estender a notificação com um
CarAppExtender
.
Uma notificação de navegação é mostrada no widget de coluna na parte de baixo da
tela do carro. Se o nível de importância da notificação estiver definido como
IMPORTANCE_HIGH
, ela também será mostrada como notificação de alerta (HUN, na sigla em inglês).
Se a importância não for definida com o método
CarAppExtender.Builder.setImportance
, a importância do canal de notificação
será usada.
O app pode definir uma PendingIntent
no
CarAppExtender
que
é enviada ao app quando o usuário toca na HUN ou no widget de coluna.
Se o método
NotificationCompat.Builder.setOnlyAlertOnce
for chamado com um valor true
, uma notificação de alta importância será emitida apenas
uma vez na HUN.
O snippet a seguir mostra como criar uma notificação de navegação:
Kotlin
NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), Intent(ACTION_OPEN_APP).setComponent( ComponentName(context, MyNotificationReceiver::class.java)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build()
Java
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( new CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), new Intent(ACTION_OPEN_APP).setComponent( new ComponentName(context, MyNotificationReceiver.class)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build();
Atualize a notificação da navegação guiada regularmente para mudanças
de distância, o que atualiza o widget de coluna, e só mostre a notificação como uma HUN.
É possível controlar o comportamento da HUN definindo a importância da notificação com
CarAppExtender.Builder.setImportance
. A definição da importância como
IMPORTANCE_HIGH
mostra uma HUN. A definição
com qualquer outro valor atualiza apenas o widget de coluna.
Atualizar o conteúdo do PlaceListNavigationTemplate
Você pode permitir que os motoristas atualizem o conteúdo com um simples toque de botão enquanto navegam
em listas de lugares criadas com o
PlaceListNavigationTemplate
.
Para ativar a atualização da lista, implemente o método
onContentRefreshRequested
da interface
OnContentRefreshListener
e use
PlaceListNavigationTemplate.Builder.setOnContentRefreshListener
para definir o listener no modelo.
O snippet a seguir mostra como definir o listener no modelo:
Kotlin
PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate() } .build()
Java
new PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener(() -> { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate(); }) .build();
O botão de atualização só aparece no cabeçalho do
PlaceListNavigationTemplate
quando o listener tem um valor.
Quando o motorista clica no botão, o
método onContentRefreshRequested
da sua implementação de OnContentRefreshListener
é chamado. No
onContentRefreshRequested
,
chame o método
Screen.invalidate
.
Em seguida, o host vai chamar o método
Screen.onGetTemplate
do app para recuperar o modelo com o conteúdo atualizado. Consulte Atualizar o
conteúdo de um modelo para saber mais sobre
a atualização de modelos. Contanto que o próximo modelo
retornado por
onGetTemplate
seja do
mesmo tipo, ele será contabilizado como uma atualização, e não na
cota do modelo.
Oferecer orientações por áudio
Para tocar a orientação de navegação nos alto-falantes do carro, seu app precisa solicitar
seleção de áudio. Como parte da
AudioFocusRequest
, defina
o uso como AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
. Além disso,
defina o ganho da seleção como AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
.
Simular a navegação
Para verificar a funcionalidade de navegação do app ao enviá-lo à
Google Play Store, é necessário que ele implemente o
callback
NavigationManagerCallback.onAutoDriveEnabled
. Quando esse callback é chamado, o app precisa simular o trajeto até o
destino escolhido quando o usuário inicia a navegação. O app poderá sair desse
modo sempre que o ciclo de vida da Session
atual atingir o
estado Lifecycle.Event.ON_DESTROY
.
Teste se a implementação de onAutoDriveEnabled
é chamada
executando o seguinte em uma linha de comando:
adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE
Isso é mostrado neste exemplo:
adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE
App de navegação padrão para carros
No Android Auto, o app de navegação padrão para carros corresponde ao último app desse tipo que o usuário iniciou. O app padrão recebe as intents de navegação quando o usuário invoca comandos pelo Google Assistente ou quando outro app envia uma intent para iniciar a navegação.
Mostrar alertas de navegação no contexto
O Alert
mostra informações
importantes para o motorista com ações opcionais sem sair do contexto
da tela de navegação. Para oferecer a melhor experiência ao motorista, o
Alert
funciona no
NavigationTemplate
para evitar o bloqueio do trajeto de navegação e minimizar a distração do motorista.
O Alert
está disponível apenas no NavigationTemplate
.
Para notificar um usuário fora do
NavigationTemplate
,
use uma notificação de alerta, conforme explicado em
Mostrar notificações.
Por exemplo, use Alert
para:
- informar o motorista sobre uma atualização relevante para a navegação atual, como uma mudança nas condições de trânsito;
- pedir ao motorista uma atualização relacionada à navegação atual, como a existência de um radar móvel;
- propor uma próxima tarefa e perguntar se o motorista a aceita. Por exemplo, se o motorista quer pegar alguém no caminho.
Na forma básica, um Alert
consiste em um título e na
duração do Alert
. A duração é representada por uma barra de progresso. Também é
possível adicionar um subtítulo, um ícone e até dois objetos
Action
.
Depois que um Alert
é mostrado, ele não
é transferido para outro modelo quando uma interação do motorista resulta na
saída do NavigationTemplate
.
Ele permanece no
NavigationTemplate
original até o Alert
expirar, o usuário
realizar uma ação ou o app dispensar o Alert
.
Criar um alerta
Use Alert.Builder
para criar uma instância de Alert
:
Kotlin
Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build()
Java
new Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build();
Para ouvir o cancelamento ou a dispensa do Alert
,
crie uma implementação da interface
AlertCallback
.
Estes são os caminhos de chamada de AlertCallback
:
Se o
Alert
expirar, o host vai chamar o métodoAlertCallback.onCancel
com o valorAlertCallback.REASON_TIMEOUT
. Em seguida, ele chamará o métodoAlertCallback.onDismiss
.Se o motorista clicar em um dos botões de ação, o host vai chamar
Action.OnClickListener
e depoisAlertCallback.onDismiss
.Se não houver suporte para o
Alert
, o host chamaráAlertCallback.onCancel
com o valorAlertCallback.REASON_NOT_SUPPORTED
. O host não chamaAlertCallback.onDismiss
porque oAlert
não foi mostrado.
Configurar a duração do alerta
Escolha uma duração de Alert
que
corresponda às necessidades do app. A duração recomendada para um Alert
de navegação é de 10 segundos. Consulte Alertas de navegação.
para mais informações.
Mostrar um alerta
Para mostrar um Alert
, chame o método
AppManager.showAlert
,
disponível no
CarContext
do app.
// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)
- Chamar
showAlert
com umAlert
que tenha umalertId
igual ao ID doAlert
mostrado na tela não gera nenhuma ação. OAlert
não é atualizado. Para atualizar umAlert
, ele precisa ser recriado com um novoalertId
. - Chamar
showAlert
com umAlert
que tenha umalertId
diferente doAlert
mostrado na tela dispensa oAlert
que está em exibição no momento.
Dispensar um alerta
Embora um Alert
seja dispensado automaticamente
devido ao tempo limite ou à interação do motorista, você também pode dispensar um
Alert
de forma manual, por exemplo, caso as informações fiquem desatualizadas. Para dispensar um
Alert
chame o
método dismissAlert
com o
alertId
do Alert
.
// Dismiss the same alert
carContext.getCarService(AppManager.class).dismissAlert(alert.getId())
Chamar dismissAlert
com um alertId
que não corresponde ao Alert
mostrado no momento não resulta em nenhuma ação. Isso não gera uma exceção.