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
Vários formatos de intent permitem que apps de navegação funcionem com outros apps, como apps de ponto de interesse e assistentes de voz.
Para oferecer suporte a esses formatos de intent, primeiro declare o suporte adicionando filtros de intent no manifesto do app. O local desses filtros de intent depende da plataforma:
- Android Auto: no elemento de manifesto
<activity>para oActivityusado para processar a intent quando um usuário não está usando o Android Auto. - Android Automotive OS: no elemento de manifesto
<activity>para oCarAppActivity.
Em seguida, leia e processe as intents nos callbacks onCreateScreen() e
onNewIntent() na implementação Session do app.
Formatos de intent obrigatórios
Para atender ao requisito de qualidade NF-6, o app precisa processar
intenções de navegação.
Formatos de intent opcionais
Você também pode oferecer suporte aos seguintes formatos de intent para aumentar ainda mais a interoperabilidade do app:
Acessar os modelos de navegação
Os apps de navegação podem acessar os seguintes modelos, que mostram uma plataforma em segundo plano com o mapa e as instruções de navegação guiada (quando ativa).
NavigationTemplate: também mostra uma mensagem informativa opcional e estimativas da viagem durante a navegação ativa.MapWithContentTemplate: um modelo que permite que um app renderize blocos de mapa com algum tipo de conteúdo (por exemplo, uma lista). O conteúdo geralmente é renderizado como uma sobreposição sobre os blocos do mapa, com as áreas visíveis e estáveis do mapa se ajustando ao conteúdo.
Para saber mais 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>
É necessária uma permissão adicional para criar mapas.
Migrar para o MapWithContentTemplate
A partir do nível 7 da API Car App, as
funções
MapTemplate,
PlaceListNavigationTemplate
e RoutePreviewNavigationTemplate
foram descontinuadas. Os modelos descontinuados vão continuar sendo aceitos, mas
a migração para o MapWithContentTemplate é altamente recomendada.
A funcionalidade fornecida por esses modelos pode ser implementada
usando MapWithContentTemplate. Confira os exemplos nos snippets abaixo:
MapTemplate
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.
- Diretriz de qualidade de apps para carros
NF-9: a tela do cluster só pode mostrar blocos de mapas. Uma rota de navegação ativa pode ser mostrada nos blocos, se você quiser. - 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
Alertexpirar, o host vai chamar o métodoAlertCallback.onCancelcom 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.OnClickListenere depoisAlertCallback.onDismiss.Se não houver suporte para o
Alert, o host chamaráAlertCallback.onCancelcom o valorAlertCallback.REASON_NOT_SUPPORTED. O host não chamaAlertCallback.onDismissporque oAlertnã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
showAlertcom umAlertque tenha umalertIdigual ao ID doAlertmostrado na tela não gera nenhuma ação. OAlertnão é atualizado. Para atualizar umAlert, ele precisa ser recriado com um novoalertId. - Chamar
showAlertcom umAlertque tenha umalertIddiferente doAlertmostrado na tela dispensa oAlertque 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.