Criar um app de navegação

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 projetados especificamente para eles. Todos esses modelos 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.
  • MapTemplate: apresenta uma versão compacta de uma lista (como na classe ListTemplate) ou de um painel (informações detalhadas com ações em destaque, como na classe PaneTemplate) ao lado de um mapa.
  • PlaceListNavigationTemplate: também mostra uma lista de lugares, que podem ter marcadores correspondentes desenhados no mapa.
  • RoutePreviewNavigationTemplate: também mostra uma lista de trajetos, em que um deles pode ser selecionado e destacado no mapa.

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:

<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>

Desenhar o mapa

Os apps de navegação podem acessar uma Surface para desenhar o mapa nos modelos relevantes.

Um objeto SurfaceContainer pode ser acessado definindo uma instância de SurfaceCallback para o serviço de carro AppManager:

Kotlin

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Java

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

A interface SurfaceCallback fornece um callback quando o SurfaceContainer está disponível, assim como outros callbacks quando as propriedades da Surface mudam.

Para ter acesso à plataforma, seu app precisa declarar a permissão androidx.car.app.ACCESS_SURFACE no arquivo AndroidManifest.xml:

<uses-permission android:name="androidx.car.app.ACCESS_SURFACE"/>

Área visível do mapa

O host pode desenhar elementos da interface do usuário para os modelos na parte de cima do mapa. Ele chama o método SurfaceCallback.onVisibleAreaChanged para comunicar a área que certamente não estará obstruída e que vai ficar totalmente visível para o usuário. Além disso, para minimizar o número de mudanças, ele chama o método SurfaceCallback.onStableAreaChanged com o menor retângulo, que sempre fica visível com base no modelo atual.

Por exemplo, quando um app de navegação usa a classe NavigationTemplate com uma faixa de ação na parte de cima, essa faixa pode ficar oculta caso o usuário não esteja interagindo com a tela há algum tempo, criando mais espaço para o mapa. Nesse caso, há um callback para onStableAreaChanged e onVisibleAreaChanged com o mesmo retângulo. Quando a faixa de ação está oculta, somente onVisibleAreaChanged é chamado com a área maior. Se o usuário interagir com a tela, novamente apenas o onVisibleAreaChanged será chamado com o primeiro retângulo.

Suporte para o modo escuro

Os apps de navegação precisam redesenhar o mapa na instância de Surface com as cores escuras adequadas quando o host determinar que as condições justificam esse uso, conforme descrito em Qualidade do app Android para carros.

Para decidir se você precisa desenhar um mapa escuro, use o método CarContext.isDarkMode. Sempre que o status do modo escuro mudar, você vai receber uma chamada para Session.onCarConfigurationChanged.

Os apps de navegação precisam comunicar outros metadados de navegação ao host. O host usa as informações para fornecer informações à unidade principal do veículo e impedir que aplicativos de navegação 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.

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.

Figura 1. Estimativa de viagem com ícone e texto personalizados.

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:

  1. Marcar a notificação como em andamento com o método NotificationCompat.Builder.setOngoing.
  2. Definir a categoria da notificação como Notification.CATEGORY_NAVIGATION.
  3. 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.

Permitir que os usuários interajam com o mapa

Você pode adicionar suporte à interação com mapas, como ao permitir que os usuários vejam diferentes partes de um mapa usando zoom e movimento. Cada modelo tem um requisito mínimo diferente de nível da API Car App. Consulte a tabela a seguir para saber o nível mínimo do modelo que você quer implementar.

ModeloInteratividade com suporte desde o nível da API Car App
NavigationTemplate2
PlaceListNavigationTemplate4
RoutePreviewNavigationTemplate4
MapTemplate5

Métodos de SurfaceCallback

A interface SurfaceCallback tem vários métodos de callback que permitem adicionar interatividade aos mapas criados com os modelos NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate ou MapTemplate: onClick, onScroll, onScale e onFling. Consulte a tabela a seguir para saber como esses callbacks estão relacionados às interações do usuário.

Interação Método SurfaceCallback Com suporte desde o nível da API Car App
Tocar onClick 5
Fazer gesto de pinça para aplicar zoom onScale 2
Arrastar com um único toque onScroll 2
Deslizar rapidamente com um único toque onFling 2
Tocar duas vezes onScale (com fator de escalonamento determinado pelo host do modelo) 2
Alerta giratório no modo "Movimentar" onScroll (com fator de distância determinado pelo host do modelo) 2

Faixa de ações no mapa

Os modelos NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate e MapTemplate podem ter uma faixa de ações no mapa para ações relacionadas a ele, como aumentar e diminuir o zoom, recentralizar, exibir uma bússola, entre outras ações que você escolher mostrar. A faixa de ações no mapa pode ter até quatro botões somente de ícones que podem ser atualizados sem afetar a profundidade da tarefa. Ela é ocultada e reaparece no estado ativo.

Para receber callbacks de interatividade, adicione um botão Action.PAN à faixa de ações no mapa. Quando o usuário pressiona o botão correspondente, o host entra no modo de movimentação, conforme descrito na seção a seguir.

Se o app omitir o botão Action.PAN na faixa de ações, ele não vai receber entradas do usuário dos métodos SurfaceCallback, e o host sairá de modos de movimentação ativados anteriormente.

Em uma tela touchscreen, o botão de movimentação não é mostrado.

Modo de movimentação

No modo de movimentação, o host do modelo converte a entrada do usuário de dispositivos de entrada sem toque, como controles giratórios e touchpads, em métodos SurfaceCallback adequados. Responda à ação do usuário para entrar ou sair do modo de movimentação com o método setPanModeListener no NavigationTemplate.Builder. O host poderá ocultar outros componentes de interface no modelo enquanto o usuário estiver no modo de movimentação.

Área estável

A área estável é atualizada entre os estados ativo e inativo. Determine se é preciso extrair informações relacionadas à direção (como velocidade, limite de velocidade ou avisos de via) com base no tamanho da área estável para que as informações importantes do mapa não sejam obstruídas pela faixa de ações.

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.

Figura 2. Alerta de navegação no contexto.

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:

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 ver 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 um Alert que tenha um alertId igual ao ID do Alert mostrado na tela não gera nenhuma ação. O Alert não é atualizado. Para atualizar um Alert, ele precisa ser recriado com um novo alertId.
  • Chamar showAlert com um Alert que tenha um alertId diferente do Alert mostrado na tela dispensa o Alert 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.