Criar um app de navegação

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Esta seção detalha os diferentes recursos da biblioteca que você pode usar 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 intents de 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 ter suporte a intents de navegação no seu app, incluindo as que vêm do Google Assistente usando uma consulta por voz, o app precisa processar a intent CarContext.ACTION_NAVIGATE nos métodos Session.onCreateScreen e Session.onNewIntent.

Consulte a documentação de CarContext.startCarApp para ver 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 apps de navegação. Todos esses modelos exibem uma plataforma em segundo plano que o app pode acessar para desenhar o mapa, junto com outras informações fornecidas pelo app que variam de acordo com o modelo.

Para ver mais detalhes sobre como projetar a interface do usuário do seu app de navegação usando esses modelos, consulte as diretrizes de design da biblioteca Android for Cars App.

Para ter acesso aos modelos de navegação, seu app precisa declarar a permissão androidx.car.app.NAVIGATION_TEMPLATES no AndroidManifest.xml:

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

Desenhar o mapa

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

Um objeto SurfaceContainer pode ser acessado ao definir 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);

O SurfaceCallback fornece um callback quando o SurfaceContainer está disponível com outros callbacks no momento em que as propriedades da Surface mudam.

Para ter acesso à plataforma, seu app precisa declarar a permissão androidx.car.app.ACCESS_SURFACE no 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 diferentes modelos na parte de cima do mapa. Ele vai comunicar a área que certamente não estará fechada e que vai ficar totalmente visível para o usuário chamando o método SurfaceCallback.onVisibleAreaChanged. Além disso, para minimizar o número de mudanças, o host também vai chamar o método SurfaceCallback.onStableAreaChanged com o menor retângulo, que sempre vai ficar visível com base no modelo atual.

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

Modo escuro

Os aplicativos de navegação precisam redesenhar o mapa na instância de Surface com as cores escuras adequadas quando o host determina que as condições as justificam, conforme descrito nas diretrizes de qualidade de apps do Android Auto.

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

Os aplicativos de navegação precisam comunicar outros metadados de navegação ao host. O host usa as informações para fornecer dados à 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 roteamento e dados de cluster do veículo, ele precisa estar ciente do estado atual da navegação. Quando um usuário inicia a navegação, o app precisa chamar NavigationManager.navigationStarted. Da mesma forma, quando a navegação termina, por exemplo, quando o usuário chega ao destino ou quando ele cancela a navegação, o app precisa chamar NavigationManager.navigationEnded.

Chame NavigationManager.navigationEnded apenas quando o usuário terminar a navegação. Por exemplo, se você precisar recalcular a rota no meio de uma viagem, use Trip.Builder.setLoading(true).

Ocasionalmente, o host vai precisar de um app para interromper a navegação e chamar stopNavigation em um objeto NavigationManagerListener fornecido pelo seu aplicativo usando NavigationManager.setListener. Para isso, o app vai 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.

Informações da viagem

Durante a navegação ativa, o app precisa chamar NavigationManager.updateTrip. As informações fornecidas nessa chamada vão ser usadas no cluster do veículo e nos avisos na tela. Nem todas as informações podem ser mostradas para o usuário, dependendo do veículo em questão. Por exemplo, a Desktop Head Unit mostra a Step adicionada à Trip, mas não mostra a informação do Destination.

Para testar se as informações estão chegando ao cluster, a ferramenta Desktop Head Unit (DHU) pode ser configurada para mostrar uma exibição de cluster simples. Crie um arquivo cluster.ini com o seguinte conteúdo:

[general]
instrumentcluster = true

É possível invocar a DHU com um parâmetro de linha de comando adicional:

dhu -c cluster.ini

Personalizar TravelEstimate com texto e/ou ícone

Para personalizar a estimativa de viagem com texto e/ou ícone, use os métodos setTripIcon de TravelEstimate.Builder e/ou setTripText. O NavigationTemplate usa 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 os métodos setTripIcon e setTripText do TravelEstimate.Builder 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();

Notificações da navegação guiada

As instruções de navegação guiada (TBT, sigla em inglês) podem ser fornecidas com uma notificação de navegaçã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 vai ser exibida 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 vai ser exibida como uma notificação de alerta (HUN, sigla em inglês). Se a importância não estiver definida com o método CarAppExtender.Builder.setImportance, a importância do canal de notificação vai ser usada.

O app pode definir uma PendingIntent no CarAppExtender que vai ser enviada ao app quando o usuário tocar na HUN ou no widget de coluna.

Se NotificationCompat.Builder.setOnlyAlertOnce for chamado com o valor true, uma notificação de alta importância vai 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();

Diretrizes para notificações de navegação guiada

Os apps de navegação precisam atualizar a notificação da navegação guiada regularmente com mudanças de distância, o que atualiza o widget de coluna e mostra a notificação apenas como uma HUN. Os apps podem controlar o comportamento da HUN definindo a importância da notificação com o método CarAppExtender.Builder.setImportance. Definir a importância como IMPORTANCE_HIGH vai mostrar uma HUN, enquanto a definição como qualquer outro valor vai apenas atualizar o widget de coluna.

Atualizar 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. Implemente o método onContentRefreshRequested da interface OnContentRefreshListener e use PlaceListNavigationTemplate.Builder.setOnContentRefreshListener para definir o listener no modelo e ativar a atualização da lista.

O snippet a seguir mostra a definição do 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 "Atualizar" só é exibido no cabeçalho do PlaceListNavigationTemplate quando o listener tem um valor.

Quando o motorista clica no botão "Atualizar", o método onContentRefreshRequested da implementação do seu OnContentRefreshListener é chamado. Em 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 Como atualizar o conteúdo de um modelo para ver mais informações sobre a atualização de modelos. Contanto que o próximo modelo retornado por onGetTemplate seja do mesmo tipo, ele vai ser contabilizado como uma atualização e não vai ser contabilizado na cota do modelo.

Orientação por voz

Para reproduzir a orientação de navegação nos alto-falantes do carro, seu app precisa solicitar seleção de áudio. Como parte da AudioFocusRequest, você precisa definir o uso como AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE. Defina também o ganho de seleção como AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Simular a navegação

Para verificar a funcionalidade de navegação do seu app ao enviá-lo à Google Play Store, o app precisa implementar 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 pode 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

Exemplo:

adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE

App padrão de navegação para carro

No Android Auto, o app padrão de navegação para carro corresponde ao último app de navegação que o usuário iniciou. Esse é o app que, por exemplo, recebe as intents de navegação quando o usuário invoca comandos de navegação pelo assistente ou quando outro app envia uma intent para iniciar a navegação.

Permitir que os usuários interajam com seu mapa

Você pode adicionar suporte à interação com mapas, por exemplo, com zoom e movimentação, permitindo que os usuários vejam diferentes partes de um mapa. Cada modelo tem um requisito mínimo diferente de nível da API Car App. Consulte a tabela abaixo para ver 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 do SurfaceCallback

O SurfaceCallback tem vários métodos de callback que permitem adicionar interatividade de mapas aos mapas criados com os modelos NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate ou MapTemplate: onClick, onScroll, onScale e onFling. Consulte a tabela abaixo 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 (zoom) onScale 2
Arrastar com um único toque onScroll 2
Deslizar rapidamente com um único toque onFling 2
Tocar duas vezes onScale (com um fator de escalonamento determinado pelo host do modelo) 2
Alerta giratório no modo Movimentar onScroll (com um 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, por exemplo, aumentar e diminuir o zoom, recentralizar, exibir uma bússola ou qualquer outra ação que seja possível exibir no app. 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. Assim como a faixa de ações, a faixa de ações no mapa fica oculta no estado inativo e reaparece no estado ativo.

Para receber callbacks de interatividade no mapa, adicione um botão Action.PAN à faixa de ações no mapa. Se o app omitir o botão Action.PAN na faixa de ações, você não receberá entradas dos usuários dos métodos SurfaceCallback, e o host fechará qualquer modo de movimentação ativado anteriormente. Quando o usuário pressiona o botão de movimentação, o host entra no modo de movimentação. Em uma tela touchscreen, o botão de movimentação não será exibido.

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 controladores 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 pode ocultar outros componentes de IU no modelo enquanto o usuário está no modo de movimentação.

Área estável

A área estável é atualizada entre os estados ativo e inativo. Seu app precisa desenhar informações relacionadas à condução, como velocidade, limite de velocidade ou avisos de rua, dependendo do tamanho da área estável, para que informações importantes não sejam escondidas pela faixa de ações no mapa.

Alertas de navegação no contexto

Um Alert exibe 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 da rota de navegação e minimizar a distração do motorista.

Alert está disponível apenas no NavigationTemplate. Para notificar um usuário fora do NavigationTemplate, use uma notificação de alerta (HUN, na sigla em inglês), conforme explicado em Exibir 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 vai aceitá-la ou não. Por exemplo, se o motorista quer pegar alguém no caminho.

Na forma básica, um Alert consiste em um título e no tempo da 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é duas Actions.

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

Depois que um Alert é exibido, ele não passa 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.

Configurar 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 as Diretrizes de design do Android para carros para ver orientações.

Criar um alerta

Use o 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. Veja os caminhos de chamada de AlertCallback:

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)

Dispensar um alerta

Embora um Alert seja dispensado automaticamente em razão do tempo limite ou da interação do motorista, você também pode dispensar um Alert manualmente. Por exemplo, é possível dispensar um Alert porque as informações dele estão 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 exibido no momento (se houver) não resulta em nenhuma ação. Isso não gera uma exceção.