1. Antes de começar
Neste codelab, você aprenderá a criar apps otimizados contra distrações para Android Auto e Android Automotive OS usando a biblioteca Android for Cars App. Primeiro, você vai adicionar suporte para o Android Auto e, em seguida, com o mínimo de trabalho adicional, vai criar uma variante do app que pode ser executada no Android Automotive OS. Depois de executar o aplicativo nas duas plataformas, você criará outra tela e interatividade básica.
O que este codelab não é
- Um guia sobre como criar apps de música (áudio) para o Android Auto e o Android Automotive OS. Consulte Criar apps de música para carros para saber como criar esses apps.
- Um guia sobre como criar apps de mensagens para o Android Auto. Consulte Criar apps de mensagens para Android Auto para saber detalhes da criação desses aplicativos.
O que é necessário
- Prévia do Android Studio. Os emuladores do Android Automotive OS estão disponíveis apenas na Prévia do Android Studio. Se você ainda não a instalou, inicie o codelab com a versão estável enquanto baixa a versão de pré-lançamento.
- Experiência com Kotlin básico.
- Conhecimento básico sobre os Serviços do Android.
- Experiência na criação de dispositivos virtuais Android e na execução deles no Android Emulator.
- Conhecimento básico sobre a modularização do app Android.
- Conhecimento básico sobre o padrão de design do Builder.
O que você vai criar
Android Auto | Android Automotive OS |
O que você vai aprender
- Como funciona a arquitetura cliente-host da biblioteca Car App.
- Como gravar suas próprias classes
CarAppService
,Session
eScreen
. - Como compartilhar sua implementação no Android Auto e no Android Automotive OS.
- Como executar o Android Auto em sua máquina de desenvolvimento usando a unidade principal do computador.
- Como executar o emulador do Android Automotive OS.
2. Começar a configuração
Acessar o código
- O código deste codelab pode ser encontrado no diretório
car-app-library-fundamentals
do repositóriocar-codelabs
do GitHub. Para clonar, execute o seguinte comando:
git clone https://github.com/android/car-codelabs.git
- Se preferir, baixe o repositório como um arquivo ZIP:
Abrir o projeto
- Depois de iniciar o Android Studio, importe o projeto, selecionando apenas o diretório
car-app-library-fundamentals/start
. O diretóriocar-app-library-fundamentals/end
contém o código da solução, que você pode consultar a qualquer momento se tiver dificuldades ou apenas quiser conferir o projeto completo.
Conhecer o código
- Depois de abrir o projeto no Android Studio, analise o código inicial.
Esse código do aplicativo é dividido em dois módulos, :app
e :common:data
.
O módulo :app
contém a interface e a lógica do app para dispositivos móveis, e o módulo :common:data
contém a classe de dados do modelo Place
e o repositório usado para ler modelos Place
. Para simplificar, o repositório faz a leitura em uma lista codificada, mas poderia facilmente ler em um banco de dados ou servidor de back-end em um aplicativo real.
O módulo :app
inclui uma dependência do módulo :common:data
para que ele possa ler e apresentar a lista de modelos Place
.
3. Informações sobre a biblioteca Android for Cars App
A biblioteca Android for Cars App é um conjunto de bibliotecas do Jetpack que permite aos desenvolvedores criar aplicativos para uso em veículos. Ela fornece uma estrutura modelada com interfaces de usuário otimizadas para a direção e, ao mesmo tempo, se adapta às várias configurações de hardware presentes nos carros (por exemplo, métodos de entrada, tamanhos de tela e proporções). Em conjunto, isso torna mais fácil para os desenvolvedores criar um aplicativo e ter a confiança de que ele terá um bom desempenho em uma variedade de veículos que executam o Android Auto e o Android Automotive OS.
Saiba como funciona
Os apps criados com a biblioteca Car App não são executados diretamente no Android Auto ou no Android Automotive OS. Em vez disso, eles contam com um aplicativo host que se comunica com apps clientes e renderiza as interfaces de usuário do cliente em nome deles. O próprio Android Auto é um host, e o Google Automotive App Host é o host usado em veículos com Android Automotive OS e o Google integrado. Veja a seguir as principais classes da biblioteca Car App que é preciso estender ao criar seu aplicativo:
CarAppService
CarAppService
é uma subclasse da classe Service
do Android e serve como ponto de entrada para aplicativos host se comunicarem com apps clientes (como o que você cria neste codelab). O principal objetivo dela é criar instâncias de Session
que interagem com o aplicativo host.
Session
A Session
é como uma instância de um app cliente em execução na tela do veículo. Como outros componentes do Android, ela tem um ciclo de vida próprio que pode ser usado para inicializar e desmontar recursos usados durante a existência da instância de Session
. Existe uma relação um para muitos entre CarAppService
e Session
. Por exemplo, um CarAppService
pode ter duas instâncias de Session
, uma em exibição primária e outra em exibição de cluster para aplicativos de navegação compatíveis com telas de cluster.
Screen
As instâncias de Screen
são responsáveis por gerar as interfaces do usuário renderizadas por aplicativos host. Essas interfaces são representadas por classes Template
com um tipo específico de layout, como uma grade ou lista. Cada Session
gerencia uma pilha de instâncias de Screen
que lidam com fluxos do usuário nas diferentes partes do seu aplicativo. Assim como acontece com Session
, uma Screen
tem um ciclo de vida próprio ao qual você pode se conectar.
Você vai escrever CarAppService
, Session
e Screen
na seção Escrever o CarAppService deste codelab, então não se preocupe com o que ainda não funciona.
4. Definir a configuração inicial
Para começar, configure o módulo que contém o CarAppService
e declare as dependências.
Criar o módulo car-app-service
- Com o módulo
:common
selecionado na janela Project, clique com o botão direito do mouse e escolha a opção New > Module. - No assistente de módulo que é aberto, selecione o modelo Android Library (para que este módulo possa ser usado como uma dependência por outros módulos) na lista do lado esquerdo e use os seguintes valores:
- Nome do módulo:
:common:car-app-service
- Nome do pacote:
com.example.places.carappservice
- SDK mínimo:
API 23: Android 6.0 (Marshmallow)
Configurar dependências
- No arquivo
build.gradle
do projeto, adicione uma declaração de variável para a versão da biblioteca Car App da seguinte maneira. Assim, é possível usar com facilidade a mesma versão em cada um dos módulos do aplicativo.
build.gradle (projeto: Places)
buildscript {
ext {
// All versions can be found at https://developer.android.com/jetpack/androidx/releases/car-app
car_app_library_version = '1.3.0-rc01'
...
}
}
- Em seguida, adicione duas dependências ao arquivo
build.gradle
do módulo:common:car-app-service
.
androidx.car.app:app
. Esse é o artefato principal da biblioteca Car App e inclui todas as classes principais usadas ao criar aplicativos. Há três outros artefatos que compõem a biblioteca,androidx.car.app:app-projected
para a funcionalidade específica do Android Auto,androidx.car.app:app-automotive
para o código de funcionalidade do Android Automotive OS eandroidx.car.app:app-testing
para alguns auxiliares úteis em testes de unidade. Você usaráapp-projected
eapp-automotive
mais adiante no codelab.:common:data
. Esse é o mesmo módulo de dados usado pelo app para dispositivos móveis e permite o uso da mesma fonte de dados para todas as versões da experiência no app.
build.gradle (módulo :common:car-app-service)
dependencies {
...
implementation "androidx.car.app:app:$car_app_library_version"
implementation project(":common:data")
...
}
Com essa mudança, o gráfico de dependência dos próprios módulos do aplicativo é o seguinte:
Agora que as dependências estão configuradas, é hora de escrever o CarAppService
.
5. Escrever o CarAppService
- Comece criando um arquivo chamado
PlacesCarAppService.kt
no pacotecarappservice
dentro do módulo:common:car-app-service
. - Nesse arquivo, crie uma classe chamada
PlacesCarAppService
, que estendeCarAppService
.
PlacesCarAppService.kt
class PlacesCarAppService : CarAppService() {
override fun createHostValidator(): HostValidator {
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
}
override fun onCreateSession(): Session {
// PlacesSession will be an unresolved reference until the next step
return PlacesSession()
}
}
A classe abstrata CarAppService
implementa métodos Service
, como onBind
e onUnbind
, e evita substituições adicionais desses métodos para garantir a interoperabilidade adequada com aplicativos host. Tudo o que você precisa fazer é implementar createHostValidator
e onCreateSession
.
O HostValidator
retornado de createHostValidator
é referenciado quando o CarAppService
está sendo associado, para que o host seja confiável e a vinculação falhe se o host não corresponder aos parâmetros definidos. Para os fins deste codelab (e dos testes em geral), o ALLOW_ALL_HOSTS_VALIDATOR
ajuda a garantir que seu app se conecte, mas não seja usado na produção. Consulte a documentação do createHostValidator
para mais informações sobre como configurar isso em um app de produção.
Para um aplicativo tão simples quanto este, onCreateSession
pode retornar uma instância de Session
. Em um app mais complicado, esse seria um bom ponto para inicializar recursos de longa duração, como métricas e clientes de registro usados enquanto seu aplicativo está em execução no veículo.
- Por fim, você precisa adicionar o elemento
<service>
que corresponde aoPlacesCarAppService
no arquivoAndroidManifest.xml
do módulo:common:car-app-service
para permitir que o sistema operacional (e, por extensão, outros apps, como hosts) saiba que ele existe.
AndroidManifest.xml (:common:car-app-service)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!--
This AndroidManifest.xml will contain all of the elements that should be shared across the
Android Auto and Automotive OS versions of the app, such as the CarAppService <service> element
-->
<application>
<service
android:name="com.example.places.carappservice.PlacesCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.POI" />
</intent-filter>
</service>
</application>
</manifest>
Há duas observações importantes aqui:
- O elemento
<action>
permite que aplicativos host (e de tela de início) encontrem o app. - O elemento
<category>
declara a categoria do aplicativo, que determina quais critérios de qualidade ele precisa satisfazer (veja mais detalhes sobre isso posteriormente). Outros valores possíveis sãoandroidx.car.app.category.NAVIGATION
eandroidx.car.app.category.IOT
.
Criar a classe PlacesSession
- Crie um arquivo
PlacesCarAppService.kt
e adicione o código abaixo:
PlacesCarAppService.kt
class PlacesSession : Session() {
override fun onCreateScreen(intent: Intent): Screen {
// MainScreen will be an unresolved reference until the next step
return MainScreen(carContext)
}
}
Para um aplicativo simples como este, você pode retornar a tela principal em onCreateScreen
. No entanto, como esse método usa Intent
como um parâmetro, um app com mais recursos também pode lê-lo e preencher uma backstack de telas ou usar alguma outra lógica condicional.
Criar a classe MainScreen
Em seguida, crie um novo pacote chamado screen.
- Clique com o botão direito do mouse no pacote
com.example.places.carappservice
e selecione New > Package (o nome completo do pacote serácom.example.places.carappservice.screen
). É aqui que você coloca todas as subclassesScreen
do aplicativo. - No pacote
screen
, crie um arquivo chamadoMainScreen.kt
para conter a classeMainScreen
, que estendeScreen
. Por enquanto, é exibida uma simples mensagem Hello, world! usando oPaneTemplate
.
MainScreen.kt
class MainScreen(carContext: CarContext) : Screen(carContext) {
override fun onGetTemplate(): Template {
val row = Row.Builder()
.setTitle("Hello, world!")
.build()
val pane = Pane.Builder()
.addRow(row)
.build()
return PaneTemplate.Builder(pane)
.setHeaderAction(Action.APP_ICON)
.build()
}
}
6. Adicionar suporte ao Android Auto
Você já implementou toda a lógica necessária para colocar o app em funcionamento, mas ainda há outras duas configurações a serem definidas antes de executá-lo no Android Auto.
Adicionar uma dependência no módulo car-app-service
No arquivo build.gradle
do módulo :app
, adicione o seguinte:
build.gradle (Módulo :app)
dependencies {
...
implementation project(path: ':common:car-app-service')
...
}
Com essa mudança, o gráfico de dependência dos próprios módulos do aplicativo é o seguinte:
Isso agrupa o código que você acabou de escrever no módulo :common:car-app-service
com outros componentes incluídos na biblioteca Car App, como a atividade de concessão de permissão fornecida.
Declarar os metadados com.google.android.gms.car.application
- Clique com o botão direito do mouse no módulo
:common:car-app-service
, selecione a opção New > Android Resource File e substitua os seguintes valores:
- Nome do arquivo:
automotive_app_desc.xml
- Tipo de recurso:
XML
- Elemento raiz:
automotiveApp
- Nesse arquivo, adicione o elemento
<uses>
a seguir para declarar que seu aplicativo usa os modelos fornecidos pela biblioteca Car App.
automotive_app_desc.xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
<uses name="template"/>
</automotiveApp>
- No arquivo
AndroidManifest.xml
do módulo:app
, adicione o seguinte elemento<meta-data>
que faz referência ao arquivoautomotive_app_desc.xml
recém-criado.
AndroidManifest.xml (:app)
<application ...>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
...
</application>
Esse arquivo é lido pelo Android Auto e informa com quais recursos seu aplicativo é compatível. Neste caso, mostra que ele usa o sistema de modelos da biblioteca Car App. Essas informações são usadas para lidar com comportamentos como adicionar o aplicativo à tela de início do Android Auto e abri-lo nas notificações.
Opcional: detectar as mudanças de projeção
Às vezes, você quer saber se o dispositivo de um usuário está ou não conectado a um carro. Para fazer isso, use a API CarConnection
. Ela fornece LiveData
que mostram o estado da conexão.
- Para usar a API
CarConnection
, primeiro adicione uma dependência ao módulo:app
no artefatoandroidx.car.app:app
.
build.gradle (módulo :app)
dependencies {
...
implementation "androidx.car.app:app:$car_app_library_version"
...
}
- Para fins de demonstração, você pode criar um elemento combinável simples, como o seguinte, que exibe o estado atual da conexão. Em um aplicativo real, esse estado pode ser capturado em algum registro, usado para desabilitar uma funcionalidade na tela do smartphone durante a projeção ou qualquer outra atividade.
MainActivity.kt
@Composable
fun ProjectionState(carConnectionType: Int, modifier: Modifier = Modifier) {
val text = when (carConnectionType) {
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not projecting"
CarConnection.CONNECTION_TYPE_NATIVE -> "Running on Android Automotive OS"
CarConnection.CONNECTION_TYPE_PROJECTION -> "Projecting"
else -> "Unknown connection type"
}
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
modifier = modifier
)
}
- Agora que há uma maneira de exibir os dados, leia-os e transmita-os para o elemento combinável, conforme demonstrado no snippet a seguir.
MainActivity.kt
setContent {
val carConnectionType by CarConnection(this).type.observeAsState(initial = -1)
PlacesTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column {
Text(
text = "Places",
style = MaterialTheme.typography.displayLarge,
modifier = Modifier.padding(8.dp)
)
ProjectionState(
carConnectionType = carConnectionType,
modifier = Modifier.padding(8.dp)
)
PlaceList(places = PlacesRepository().getPlaces())
}
}
}
}
- Se você executar o app, será exibida a mensagem Not projecting.
7. Testar com a unidade principal da área de trabalho (DHU, na sigla em inglês)
Com o CarAppService
implementado e a configuração do Android Auto em vigor, é hora de executar o app e ver como ele fica.
- Instale o aplicativo em seu smartphone e siga as instruções de como instalar e executar a DHU.
Com a DHU em funcionamento, deve aparecer o ícone do app na tela de início. Caso contrário, verifique se você seguiu todas as etapas da seção anterior, depois saia e reinicie a DHU no terminal.
Ops, travou!
- Para ver por que o aplicativo travou, ative o ícone de depuração no canto superior direito (visível apenas ao executar na DHU) ou verifique o Logcat no Android Studio.
Error: [type: null, cause: null, debug msg: java.lang.IllegalArgumentException: Min API level not declared in manifest (androidx.car.app.minCarApiLevel) at androidx.car.app.AppInfo.retrieveMinCarAppApiLevel(AppInfo.java:143) at androidx.car.app.AppInfo.create(AppInfo.java:91) at androidx.car.app.CarAppService.getAppInfo(CarAppService.java:380) at androidx.car.app.CarAppBinder.getAppInfo(CarAppBinder.java:255) at androidx.car.app.ICarApp$Stub.onTransact(ICarApp.java:182) at android.os.Binder.execTransactInternal(Binder.java:1285) at android.os.Binder.execTransact(Binder.java:1244) ]
No registro, observe que há uma declaração ausente no manifesto para o nível mínimo da API compatível com o aplicativo. Antes de adicionar essa entrada, é melhor entender por que ela é necessária.
Assim como o próprio Android, a biblioteca Car App tem um conceito de níveis de API, porque é necessário haver um contrato entre os aplicativos host e cliente para que eles se comuniquem. Os aplicativos host são compatíveis com um determinado nível de API e seus recursos associados (e com recursos de níveis anteriores também). Por exemplo, o SignInTemplate
pode ser usado em hosts que executam a API de nível 2 ou superior. No entanto, se você tentasse usá-lo em um host compatível apenas com o nível 1 da API, esse host não conheceria o tipo de modelo e não conseguiria fazer nada significativo com ele.
Durante o processo de vinculação do host ao cliente, deve haver alguma sobreposição nos níveis de API compatíveis para que a vinculação seja bem-sucedida. Por exemplo, se um host for compatível apenas com a API de nível 1, mas um aplicativo cliente não puder ser executado sem recursos da API de nível 2 (conforme indicado por essa declaração de manifesto), os apps não deverão se conectar porque o cliente não poderá ser executado no host. Assim, o nível mínimo de API necessário deve ser declarado pelo cliente no manifesto para garantir que apenas um host compatível esteja vinculado a ele.
- Para definir o nível mínimo de API com suporte, adicione o seguinte elemento
<meta-data>
ao arquivoAndroidManfiest.xml
do módulo:common:car-app-service
:
AndroidManifest.xml (:common:car-app-service)
<application>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1" />
<service android:name="com.example.places.carappservice.PlacesCarAppService" ...>
...
</service>
</application>
- Instale o app novamente e inicie-o na DHU. Em seguida, você verá o seguinte:
Por uma questão de integridade, você também pode tentar definir o minCarApiLevel
como um valor grande (por exemplo, 100) para ver o que acontece quando você tenta iniciar o aplicativo se o host e o cliente não forem compatíveis (dica: ele trava, semelhante a quando nenhum valor é definido).
Também é importante observar que, assim como no próprio Android, você pode usar recursos de uma API superior ao mínimo declarado se verificar em tempo de execução se o host dá suporte ao nível necessário.
Opcional: detectar as mudanças de projeção
- Se você adicionou o listener
CarConnection
na etapa anterior, deverá ver a atualização de estado em seu smartphone quando a DHU estiver em execução, conforme mostrado abaixo:
8. Adicionar suporte ao Android Automotive OS
Com o Android Auto em funcionamento, é hora de ir além e oferecer suporte ao Android Automotive OS também.
Criar o módulo :automotive
- Para criar um módulo que contém o código específico da versão do app Android Automotive OS, abra File > New > New Module... no Android Studio, selecione a opção Automotive na lista de tipos de modelo à esquerda e use os seguintes valores:
- Nome do aplicativo/biblioteca:
Places
(o mesmo que o aplicativo principal, mas você também pode escolher um nome diferente, se quiser) - Nome do módulo:
automotive
- Nome do pacote:
com.example.places.automotive
- Idioma:
Kotlin
- SDK mínimo:
API 29: Android 10.0 (Q)
, conforme já mencionado ao criar o módulo:common:car-app-service
, todos os veículos Android Automotive OS compatíveis com aplicativos da biblioteca Car App executam pelo menos a API 29.
- Clique em Next e selecione No Activity na próxima tela antes de clicar em Finish.
Adicionar dependências
Assim como no Android Auto, é preciso declarar uma dependência do módulo :common:car-app-service
. Dessa forma, você pode compartilhar sua implementação nas duas plataformas.
Além disso, adicione uma dependência ao artefato androidx.car.app:app-automotive
. Ao contrário do artefato androidx.car.app:app-projected
, que é opcional para o Android Auto, essa dependência é necessária no Android Automotive OS, porque inclui a CarAppActivity
usada para executar seu app.
- Para adicionar dependências, abra o arquivo
build.gradle
e insira o seguinte código:
build.gradle (módulo :automotive)
dependencies {
...
implementation project(':common:car-app-service')
implementation "androidx.car.app:app-automotive:$car_app_library_version"
...
}
Com essa mudança, o gráfico de dependência dos próprios módulos do aplicativo é o seguinte:
Configurar o manifesto
- Primeiro, você precisa declarar dois recursos,
android.hardware.type.automotive
eandroid.software.car.templates_host
, como obrigatórios.
android.hardware.type.automotive
é um recurso do sistema que indica que o dispositivo é um veículo (consulte FEATURE_AUTOMOTIVE
para mais detalhes). Somente os apps que marcam esse recurso como obrigatório podem ser enviados para uma faixa do Automotive OS no Play Console (os apps enviados para outras faixas não podem exigir esse recurso). android.software.car.templates_host
é um recurso do sistema presente apenas em veículos que têm o host necessário para executar aplicativos de modelo.
AndroidManifest.xml (:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<uses-feature
android:name="android.software.car.templates_host"
android:required="true" />
...
</manifest>
- Em seguida, você precisa declarar alguns recursos como não obrigatórios.
Isso garante que seu app seja compatível com a variedade de hardware disponível em carros com o Google integrado. Por exemplo, se o aplicativo exigir o recurso android.hardware.screen.portrait
, ele não será compatível com veículos em que a tela está no modo paisagem, porque a orientação é fixa na maioria dos carros. É por isso que o atributo android:required
é definido como false
para esses recursos.
AndroidManifest.xml (:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
...
</manifest>
- Em seguida, você precisa adicionar uma referência ao arquivo
automotive_app_desc.xml
como fez para o Android Auto.
Agora, o atributo android:name
é diferente: em vez de com.google.android.gms.car.application
, é com.android.automotive
. Assim como antes, isso faz referência ao arquivo automotive_app_desc.xml
do módulo :common:car-app-service
, o que significa que o mesmo recurso é usado no Android Auto e no Android Automotive OS. O elemento <meta-data>
está dentro do elemento <application>
, portanto, desative o fechamento automático da tag application
.
AndroidManifest.xml (:automotive)
<application>
...
<meta-data android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"/>
...
</application>
- Por fim, você precisa adicionar um elemento
<activity>
para aCarAppActivity
incluída na biblioteca.
AndroidManifest.xml (:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<application ...>
...
<activity
android:name="androidx.car.app.activity.CarAppActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="distractionOptimized"
android:value="true" />
</activity>
</application>
</manifest>
Confira o que tudo isso faz:
android:name
lista o nome da classe totalmente qualificada da classeCarAppActivity
do pacoteapp-automotive
.android:exported
está definido comotrue
porque essaActivity
precisa ser iniciada por um aplicativo diferente de si mesmo (app de tela de início).android:launchMode
está definido comosingleTask
para só haver uma instância daCarAppActivity
por vez.android:theme
é definido como@android:style/Theme.DeviceDefault.NoActionBar
para que o app ocupe todo o espaço disponível em tela cheia.- O filtro de intent indica que essa é a
Activity
de acesso rápido do app. - Há um elemento
<meta-data>
que informa ao sistema que o app pode ser usado enquanto as restrições de UX estão em vigor, como quando o veículo está em movimento.
Opcional: copiar os ícones na tela de início do módulo :app
Como você acabou de criar o módulo :automotive
, ele tem os ícones verdes padrão do logotipo do Android.
- Se quiser, copie e cole o diretório de recursos
mipmap
do módulo:app
no módulo:automotive
para usar os mesmos ícones na tela de início que o app para dispositivos móveis.
9. Testar com o emulador do Android Automotive OS
Instalar as imagens do sistema do Automotive com a Play Store
- Primeiro, abra o SDK Manager no Android Studio e selecione a guia SDK Platforms, se ela ainda não estiver selecionada. No canto inferior direito da janela do SDK Manager, verifique se a caixa Show package details está marcada.
- Instale uma ou mais das seguintes imagens do emulador. As imagens só podem ser executadas em máquinas com a mesma arquitetura (x86/ARM) que elas.
- Android 12L > Automotive with Play Store Intel x86 Atom_64 System Image
- Android 12L > Automotive with Play Store ARM 64 v8a System Image
- Android 11 > Automotive with Play Store Intel x86 Atom_64 System Image
- Android 10 > Automotive with Play Store Intel x86 Atom_64 System Image
Criar um Dispositivo Virtual Android para o Android Automotive OS
- Depois de abrir o Gerenciador de dispositivos, selecione Automotive na coluna Category no lado esquerdo da janela. Em seguida, selecione a definição de dispositivo Automotive (1024p landscape) na lista e clique em Next.
- Na próxima página, selecione uma imagem do sistema da etapa anterior. Se você escolheu a imagem do Android 11/API 30, ela pode estar na guia x86 Images, e não na guia Recommended padrão. Clique em Next e selecione qualquer opção avançada antes de clicar em Finish para criar o AVD.
Executar o app
- Execute o app no emulador recém-criado usando a configuração de execução do
automotive
.
Ao executar o aplicativo pela primeira vez, pode ser exibida uma tela como a seguinte:
Se esse for o caso, clique no botão Check for updates, que leva você à página da Play Store do aplicativo Google Automotive App Host. Depois, clique no botão Instalar. Se você não tiver feito login antes de clicar no botão Check for updates, será exibido o fluxo de entrada. Após o login, você pode abrir o app novamente para clicar no botão e voltar à página da Play Store.
- Por fim, com o host instalado, abra de novo o aplicativo na tela de início (o ícone de grade de nove pontos na linha inferior) e você verá o seguinte:
Na próxima etapa, você fará mudanças no módulo :common:car-app-service
para exibir a lista de lugares e permitir que o usuário inicie a navegação para um local escolhido em outro app.
10. Adicionar um mapa e uma tela de detalhes
Adicionar um mapa à tela principal
- Para começar, substitua o código no método
onGetTemplate
da classeMainScreen
pelo seguinte:
MainScreen.kt
override fun onGetTemplate(): Template {
val placesRepository = PlacesRepository()
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage("No places to show")
placesRepository.getPlaces()
.forEach {
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
// Each item in the list *must* have a DistanceSpan applied to either the title
// or one of the its lines of text (to help drivers make decisions)
.addText(SpannableString(" ").apply {
setSpan(
DistanceSpan.create(
Distance.create(Math.random() * 100, Distance.UNIT_KILOMETERS)
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
)
})
.setOnClickListener { TODO() }
// Setting Metadata is optional, but is required to automatically show the
// item's location on the provided map
.setMetadata(
Metadata.Builder()
.setPlace(Place.Builder(CarLocation.create(it.latitude, it.longitude))
// Using the default PlaceMarker indicates that the host should
// decide how to style the pins it shows on the map/in the list
.setMarker(PlaceMarker.Builder().build())
.build())
.build()
).build()
)
}
return PlaceListMapTemplate.Builder()
.setTitle("Places")
.setItemList(itemListBuilder.build())
.build()
}
Esse código lê a lista de instâncias de Place
no PlacesRepository
e converte cada uma delas em uma Row
a ser adicionada à ItemList
exibida pelo PlaceListMapTemplate
.
- Execute o aplicativo de novo (em uma ou ambas as plataformas) para ver o resultado.
Android Auto | Android Automotive OS |
Ops, outro erro: parece que está faltando uma permissão.
java.lang.SecurityException: The car app does not have a required permission: androidx.car.app.MAP_TEMPLATES at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) at android.os.Parcel.createException(Parcel.java:2357) at android.os.Parcel.readException(Parcel.java:2340) at android.os.Parcel.readException(Parcel.java:2282) ...
- Para corrigir o erro, adicione o seguinte elemento
<uses-permission>
no manifesto do módulo:common:car-app-service
.
Essa permissão precisa ser declarada por qualquer aplicativo que use o PlaceListMapTemplate
. Caso contrário, o aplicativo falha, conforme demonstrado. Apenas os apps que declaram sua categoria como androidx.car.app.category.POI
podem usar esse modelo e, por sua vez, essa permissão.
AndroidManifest.xml (:common:car-app-service)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
...
</manifest>
Se você executar o app depois de adicionar a permissão, ele deverá ter a seguinte aparência em cada plataforma:
Android Auto | Android Automotive OS |
O host do aplicativo cuida da renderização do mapa quando você fornece os Metadata
necessários.
Adicionar uma tela de detalhes
A seguir, vamos adicionar uma tela de detalhes para que os usuários vejam mais informações sobre um local específico e tenham a opção de navegar até esse local com o app de navegação preferido ou retornar à lista de outros lugares. Isso pode ser feito usando o PaneTemplate
, que permite exibir até quatro linhas de informações ao lado de botões de ação opcionais.
- Primeiro, clique com o botão direito do mouse no diretório
res
do módulo:common:car-app-service
, clique em New > Vector Asset e crie um ícone de navegação usando a seguinte configuração:
- Tipo de recurso:
Clip art
- Clip art:
navigation
- Nome:
baseline_navigation_24
- Tamanho:
24
dp por24
dp - Cor:
#000000
- Opacidade:
100%
- Em seguida, no pacote
screen
, crie um arquivo chamadoDetailScreen.kt
(ao lado do arquivoMainScreen.kt
) e adicione o seguinte código:
DetailScreen.kt
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
override fun onGetTemplate(): Template {
val place = PlacesRepository().getPlace(placeId)
?: return MessageTemplate.Builder("Place not found")
.setHeaderAction(Action.BACK)
.build()
val navigateAction = Action.Builder()
.setTitle("Navigate")
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.baseline_navigation_24
)
).build()
)
// Only certain intent actions are supported by `startCarApp`. Check its documentation
// for all of the details. To open another app that can handle navigating to a location
// you must use the CarContext.ACTION_NAVIGATE action and not Intent.ACTION_VIEW like
// you might on a phone.
.setOnClickListener { carContext.startCarApp(place.toIntent(CarContext.ACTION_NAVIGATE)) }
.build()
return PaneTemplate.Builder(
Pane.Builder()
.addAction(navigateAction)
.addRow(
Row.Builder()
.setTitle("Coordinates")
.addText("${place.latitude}, ${place.longitude}")
.build()
).addRow(
Row.Builder()
.setTitle("Description")
.addText(place.description)
.build()
).build()
)
.setTitle(place.name)
.setHeaderAction(Action.BACK)
.build()
}
}
Preste atenção especial em como a navigateAction
é construída: a chamada para o startCarApp
no OnClickListener
é a chave para interagir com outros aplicativos no Android Auto e no Android Automotive OS.
Navegar entre telas
Agora que existem dois tipos de telas, é hora de adicionar navegação entre elas. A navegação na biblioteca Car App usa um modelo de pilha de envio e destaque que é ideal para os fluxos de tarefas simples adequados para realização ao dirigir.
- Para navegar de um dos itens da lista na
MainScreen
até umaDetailScreen
desse item, adicione o seguinte código:
MainScreen.kt
Row.Builder()
...
.setOnClickListener { screenManager.push(DetailScreen(carContext, it.id)) }
...
A navegação de volta de uma DetailScreen
para a MainScreen
já foi tratada, porque setHeaderAction(Action.BACK)
é chamado ao criar o PaneTemplate
exibido na DetailScreen
. Quando um usuário clica na ação de cabeçalho, o host lida com a retirada da tela atual da pilha, mas esse comportamento pode ser substituído pelo seu app, se desejado.
- Execute o aplicativo agora para ver a
DetailScreen
e a navegação no aplicativo em ação.
11. Atualizar o conteúdo em uma tela
Muitas vezes, você quer permitir que um usuário interaja com uma tela e mude o estado dos elementos. Para demonstrar como fazer isso, crie uma funcionalidade que permita aos usuários alternar entre adicionar e remover um lugar dos favoritos na DetailScreen
.
- Primeiro, adicione uma variável local,
isFavorite
, que contém o estado. Em um aplicativo real, isso deve ser armazenado como parte da camada de dados, mas uma variável local é suficiente para fins de demonstração.
DetailScreen.kt
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
private var isFavorite = false
...
}
- Depois, clique com o botão direito do mouse no diretório
res
do módulo:common:car-app-service
, clique em New > Vector Asset e crie um ícone de favorito usando a seguinte configuração:
- Tipo de recurso:
Clip art
- Nome:
baseline_favorite_24
- Clip art:
favorite
- Tamanho:
24
dp por24
dp - Cor:
#000000
- Opacidade:
100%
- Depois, em
DetailsScreen.kt
, crie umaActionStrip
para oPaneTemplate
.
Os componentes da UI de ActionStrip
são colocados na linha de cabeçalho oposta ao título e são ideais para ações secundárias e terciárias. Como a navegação é a principal ação a ser realizada na DetailScreen
, colocar a Action
de adicionar ou remover dos favoritos em uma ActionStrip
é uma ótima maneira de estruturar a tela.
DetailScreen.kt
val navigateAction = ...
val actionStrip = ActionStrip.Builder()
.addAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.baseline_favorite_24
)
).setTint(
if (isFavorite) CarColor.RED else CarColor.createCustom(
Color.LTGRAY,
Color.DKGRAY
)
).build()
)
.setOnClickListener {
isFavorite = !isFavorite
}.build()
)
.build()
...
Há dois pontos importantes aqui:
- A cor do
CarIcon
depende do estado do item. setOnClickListener
é usado para reagir às entradas do usuário e alternar o estado favorito.
- Não se esqueça de chamar
setActionStrip
noPaneTemplate.Builder
para usá-la.
DetailScreen.kt
return PaneTemplate.Builder(...)
...
.setActionStrip(actionStrip)
.build()
- Agora, execute o app e veja o que acontece:
Interessante... parece que os cliques estão acontecendo, mas a interface não é atualizada.
Isso porque a biblioteca Car App tem um conceito de atualizações. Para limitar a distração do motorista, a atualização do conteúdo na tela tem certas limitações, que variam de acordo com o modelo em exibição. Cada atualização precisa ser solicitada explicitamente pelo seu código chamando o método invalidate
da classe Screen
. Apenas atualizar algum estado referenciado em onGetTemplate
não é suficiente para atualizar a UI.
- Para corrigir esse problema, atualize o
OnClickListener
da seguinte maneira:
DetailScreen.kt
.setOnClickListener {
isFavorite = !isFavorite
// Request that `onGetTemplate` be called again so that updates to the
// screen's state can be picked up
invalidate()
}
- Execute o app novamente para ver se a cor do ícone de coração é atualizada a cada clique.
Agora, você tem um aplicativo básico bem integrado ao Android Auto e ao Android Automotive OS.
12. Parabéns
Você criou seu primeiro aplicativo da biblioteca Car App. Agora é hora de aplicar em seu próprio aplicativo o que você aprendeu.
Como mencionado anteriormente, apenas algumas categorias criadas usando os aplicativos da biblioteca Car App podem ser enviadas para a Play Store no momento. No caso de um app de navegação, de ponto de interesse (PDI) (como aquele com que você trabalhou neste codelab) ou de Internet das Coisas (IoT), você poderá começar a criar hoje mesmo e lançar o app até a produção nas duas plataformas.
Novas categorias de aplicativos são adicionadas todos os anos, portanto, mesmo que você não possa aplicar imediatamente o que aprendeu, volte depois e talvez seja a hora certa de estender seu app para o carro.
O que testar
- Instale um emulador do OEM (por exemplo, o Polestar 2) e veja como a personalização do OEM pode mudar a aparência dos apps da biblioteca Car App no Android Automotive OS. Nem todos os emuladores do OEM são compatíveis com aplicativos dessa biblioteca.
- Confira o aplicativo de exemplo Showcase, que demonstra a funcionalidade completa da biblioteca Car App.
Leia mais
- Usar a biblioteca Android for Cars App aborda o conteúdo deste codelab e muito mais.
- As Diretrizes de design da biblioteca Android for Cars App fornecem uma descrição detalhada de todos os diferentes modelos e práticas recomendadas para criar seu aplicativo.
- A página Qualidade do app Android para carros descreve os critérios que o app precisa satisfazer para criar uma ótima experiência do usuário e ser aprovado na avaliação da Play Store.