1. Antes de começar
O que este codelab não é
- Um guia sobre como criar apps de música (áudio, por exemplo, músicas, rádios, podcasts) para o Android Auto e o Android Automotive OS. Consulte Criar apps de música para carros para saber como criar esses apps.
O que é necessário
- Prévia do Android Studio. Alguns emuladores do Android Automotive OS usados neste codelab só estão disponíveis na Prévia do Android Studio. Se você ainda não tiver a Prévia do Android Studio instalada, inicie o codelab com a versão estável enquanto baixa a versão de pré-lançamento.
- Experiência com Kotlin básico.
- Experiência na criação de dispositivos virtuais Android e na execução deles no Android Emulator.
- Conhecimentos básicos sobre o Jetpack Compose.
- Compreensão dos efeitos colaterais
- Conhecimento básico sobre encartes de janela.
O que você vai criar
Neste codelab, você vai aprender a migrar um app para dispositivos móveis de stream de vídeo, chamado Road Reels, para o Android Automotive OS.
A versão inicial do app em execução em um smartphone | A versão completa do app em execução em um emulador do Android Automotive OS com corte da tela. |
O que você vai aprender
- Como usar o emulador do Android Automotive OS
- Como fazer as mudanças necessárias para criar um build do Android Automotive OS
- Suposições comuns feitas ao desenvolver apps para dispositivos móveis que podem ser violadas quando um app é executado no Android Automotive OS
- Os diferentes níveis de qualidade para apps em carros
- Como usar a sessão de mídia para permitir que outros apps controlem a reprodução do seu app
- Como a interface do sistema e os encartes de janela podem ser diferentes em dispositivos do Android Automotive OS em comparação com dispositivos móveis
2. Começar a configuração
Acessar o código
- O código deste codelab pode ser encontrado no diretório
build-a-parked-app
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
build-a-parked-app/start
. O diretóriobuild-a-parked-app/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.
3. Aprender sobre apps do Android Automotive OS para carros estacionados
Os apps para carros estacionados são um subconjunto das categorias de apps compatíveis com o Android Automotive OS. No momento, eles consistem em apps de streaming de vídeo, navegadores da Web e jogos. Esses apps são ideais para uso em carros, considerando o hardware presente em veículos com o Google integrado e a crescente prevalência de veículos elétricos, em que o tempo de carregamento é uma ótima oportunidade para motoristas e passageiros interagirem com esses tipos de apps.
De muitas maneiras, os carros são semelhantes a outros dispositivos de tela grande, como tablets e dobráveis. Eles têm telas touch com tamanhos, resoluções e proporções semelhantes e que podem estar na orientação retrato ou paisagem (embora, diferentemente dos tablets, a orientação seja fixa). Também são dispositivos conectados que podem entrar e sair da conexão de rede. Com tudo isso em mente, não é de surpreender que os apps já otimizados para telas grandes geralmente exijam uma quantidade mínima de trabalho para oferecer uma ótima experiência do usuário em carros.
Assim como em telas grandes, também há níveis de qualidade de apps em carros:
- Nível 3, pronto para carros: o app é compatível com telas grandes e pode ser usado enquanto o carro está estacionado. Embora o app não tenha recursos otimizados para carros, os usuários podem usar o app da mesma forma que faria em qualquer outro dispositivo Android de tela grande. Os apps para dispositivos móveis que satisfazem esses requisitos podem ser distribuídos para carros sem alterações pelo programa de apps para dispositivos móveis criados para carros.
- Nível 2, otimizado para carros: o app oferece uma ótima experiência na tela central do carro. Para isso, o app precisa ter uma engenharia específica para incluir recursos que possam ser usados nos modos de direção ou estacionado, dependendo da categoria do app.
- Nível 1, diferenciado para carros: o app é criado para funcionar com diversos tipos de hardware em carros e pode adaptar a experiência nos modos de direção e estacionado. Ele oferece a melhor experiência do usuário projetada para as diferentes telas dos carros, como o console central, o painel de instrumentos e outras telas, como telas panorâmicas encontradas em muitos carros premium.
4. Executar o app no 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 das imagens do emulador do Automotive com a Play Store listadas em Adicionar imagens genéricas do sistema. As imagens só podem ser executadas em máquinas com a mesma arquitetura (x86/ARM) que elas.
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 o perfil de hardware do pacote Automotive (1024p landscape) na lista e clique em Next.
- Na próxima página, selecione a imagem do sistema da etapa anterior. Clique em Next e selecione qualquer opção avançada que você queira antes de criar o AVD. Para isso, clique em Finish. Observação: se você escolheu a imagem da API 30, ela pode estar em uma guia diferente da Recommended.
Executar o app
Execute o app no emulador recém-criado usando a configuração de execução do app
atual. Teste o app para conferir as diferentes telas e comparar o comportamento dele com a execução em um emulador de smartphone ou tablet.
5. Criar um build do Android Automotive OS
Embora o app "simplesmente funcione", algumas pequenas mudanças precisam ser feitas para que ele funcione bem no Android Automotive OS e satisfaça os requisitos para publicação na Play Store. Nem todas essas mudanças fazem sentido incluir na versão para dispositivos móveis do app. Por isso, primeiro crie uma variante de build do Android Automotive OS.
Adicionar uma dimensão de variações de formato
Para começar, adicione uma dimensão de variações ao formato segmentado pelo build modificando as flavorDimensions
no arquivo build.gradle.kts
. Em seguida, adicione um bloco productFlavors
e variações para cada formato (mobile
e automotive
).
Para saber mais, consulte Configurar variações de produtos.
build.gradle.kts (módulo :app)
android {
...
flavorDimensions += "formFactor"
productFlavors {
create("mobile") {
// Inform Android Studio to use this flavor as the default (e.g. in the Build Variants tool window)
isDefault = true
// Since there is only one flavor dimension, this is optional
dimension = "formFactor"
}
create("automotive") {
// Since there is only one flavor dimension, this is optional
dimension = "formFactor"
// Adding a suffix makes it easier to differentiate builds (e.g. in the Play Console)
versionNameSuffix = "-automotive"
}
}
...
}
Depois de atualizar o arquivo build.gradle.kts
, um banner vai aparecer na parte de cima do arquivo informando que "Os arquivos do Gradle mudaram desde a última sincronização do projeto. Uma sincronização de projeto pode ser necessária para que o ambiente de desenvolvimento integrado "funcione corretamente". Clique no botão Sync Now nesse banner para que o Android Studio possa importar essas mudanças na configuração do build.
Em seguida, abra a janela de ferramentas Build Variants no item de menu Build > Select Build Variant… e selecione a variante automotiveDebug
. Isso garante que você veja os arquivos do conjunto de origem automotive
na janela Project e que essa variante de build seja usada ao executar o app no Android Studio.
Criar um manifesto do Android Automotive OS
Em seguida, você vai criar um arquivo AndroidManifest.xml
para o conjunto de origem automotive
. Esse arquivo contém os elementos necessários dos apps do Android Automotive OS.
- Na janela Project, clique com o botão direito do mouse no módulo
app
. No menu suspenso exibido, selecione New > Other > Android Manifest File - Na janela New Android Component que é aberta, selecione
automotive
como o Target Source Set do novo arquivo. Clique em Finish para criar o arquivo.
- No arquivo
AndroidManifest.xml
que acabou de ser criado (no caminhoapp/src/automotive/AndroidManifest.xml
), adicione o seguinte:
AndroidManifest.xml (automotivo)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://developer.android.com/training/cars/parked#required-features -->
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<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" />
</manifest>
A primeira declaração é necessária para fazer upload do artefato do build para a faixa do Android Automotive OS no Play Console. A presença desse recurso é usada pelo Google Play para distribuir o app apenas para dispositivos que tenham o recurso android.hardware.type.automotive
, ou seja, carros.
As outras declarações são necessárias para garantir que o app possa ser instalado nas várias configurações de hardware presentes em carros. Para mais detalhes, consulte Recursos necessários do Android Automotive OS.
Marcar como um app de vídeo
O último metadado que precisa ser adicionado é o arquivo automotive_app_desc.xml
. Ele é usado para declarar a categoria do app no contexto do Android para carros e é independente da categoria selecionada para o app no Play Console.
- Clique com o botão direito do mouse no módulo
app
, selecione a opção New > Android Resource File e insira os seguintes valores antes de clicar em OK:
- Nome do arquivo:
automotive_app_desc.xml
- Tipo de recurso:
XML
- Elemento raiz:
automotiveApp
- Conjunto de origem:
automotive
- Nome do diretório:
xml
- Nesse arquivo, adicione o seguinte elemento
<uses>
para declarar que o app é de vídeo.
automotive_app_desc.xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses
name="video"
tools:ignore="InvalidUsesTagAttribute" />
</automotiveApp>
- No arquivo
AndroidManifest.xml
do conjunto de origemautomotive
(em que você acabou de adicionar os elementos<uses-feature>
), adicione um elemento<application>
vazio. Nele, adicione o seguinte elemento<meta-data>
que faz referência ao arquivoautomotive_app_desc.xml
que você acabou de criar.
AndroidManifest.xml (automotivo)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<application>
<meta-data
android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc" />
</application>
</manifest>
Com isso, você fez todas as mudanças necessárias para criar um build do Android Automotive OS do app.
6. Satisfazer os requisitos de qualidade do Android Automotive OS: navegabilidade
Criar uma variante de build do Android Automotive OS faz parte do processo de levar seu app para carros. No entanto, ainda é necessário garantir que ele seja utilizável e seguro.
Adicionar affordances de navegação
Ao executar o app no emulador do Android Automotive OS, você pode ter notado que não era possível voltar da tela de detalhes para a tela principal ou da tela do player para a tela de detalhes. Diferente de outros formatos, que podem exigir um botão "Voltar" ou um gesto de toque para ativar a navegação de retorno, esse requisito não existe para dispositivos Android Automotive OS. Dessa forma, os apps precisam fornecer affordances de navegação na interface para garantir que os usuários possam navegar sem ficar presos em uma tela do app. Esse requisito é codificado como a diretriz de qualidade AN-1.
Para oferecer suporte à navegação de retorno da tela de detalhes para a tela principal, adicione outro parâmetro navigationIcon
à CenterAlignedTopAppBar
da tela de detalhes da seguinte maneira:
RoadReelsApp.kt
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
...
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
Para oferecer suporte à navegação de retorno da tela do player para a tela principal:
- Atualize o elemento combinável
TopControls
para usar um parâmetro de callback chamadoonClose
e adicione umIconButton
que o chame quando clicado.
PlayerControls.kt
@Composable
fun TopControls(
title: String?,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Box(modifier) {
IconButton(
modifier = Modifier
.align(Alignment.TopStart),
onClick = onClose
) {
Icon(
Icons.TwoTone.Close,
contentDescription = "Close player",
tint = Color.White
)
}
if (title != null) { ... }
}
}
- Atualize o elemento combinável
PlayerControls
para também usar um parâmetro de callbackonClose
e transmiti-lo aosTopControls
.
PlayerControls.kt
fun PlayerControls(
visible: Boolean,
playerState: PlayerState,
onClose: () -> Unit,
onPlayPause: () -> Unit,
onSeek: (seekToMillis: Long) -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
Box(modifier = modifier.background(Color.Black.copy(alpha = .5f))) {
TopControls(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.screen_edge_padding))
.align(Alignment.TopCenter),
title = playerState.mediaMetadata.title?.toString(),
onClose = onClose
)
...
}
}
}
- Em seguida, atualize o elemento combinável
PlayerScreen
para usar o mesmo parâmetro e transmita aosPlayerControls
.
PlayerScreen.kt
@Composable
fun PlayerScreen(
onClose: () -> Unit,
modifier: Modifier = Modifier,
) {
...
PlayerControls(
modifier = Modifier
.fillMaxSize(),
visible = isShowingControls,
playerState = playerState,
onClose = onClose,
onPlayPause = { if (playerState.isPlaying) player.pause() else player.play() },
onSeek = { player.seekTo(it) }
)
}
- Por fim, no
RoadReelsNavHost
, forneça a implementação que é transmitida para aPlayerScreen
:
RoadReelsNavHost.kt
composable(route = Screen.Player.name) {
PlayerScreen(onClose = { navController.popBackStack() })
}
Ótimo! Agora o usuário pode navegar entre as telas sem encontrar nenhuma rua sem saída. A experiência do usuário pode ser ainda melhor em outros formatos. Por exemplo, em um smartphone alto, quando a mão do usuário já está perto da parte de cima da tela, ele pode navegar pelo app com mais facilidade sem precisar mover o dispositivo na mão.
Adaptar ao suporte à orientação da tela
Ao contrário da grande maioria dos dispositivos móveis, a maioria dos carros tem orientação fixa. Ou seja, eles oferecem suporte para o modo paisagem ou retrato, mas não para ambos, já que as telas não podem ser giradas. Por isso, os apps precisam evitar presumir que há suporte para as duas orientações.
Em Criar um manifesto do Android Automotive OS, você adicionou dois elementos <uses-feature>
aos recursos android.hardware.screen.portrait
e android.hardware.screen.landscape
com o atributo required
definido como false
. Isso garante que nenhuma dependência implícita de recursos em qualquer orientação de tela possa impedir que o app seja distribuído para carros. No entanto, esses elementos do manifesto não mudam o comportamento do app, apenas a forma como ele é distribuído.
Atualmente, o app tem um recurso útil que define automaticamente a orientação da atividade como paisagem quando o player de vídeo é aberto. Assim, os usuários de smartphone não precisam mexer nele para mudar a orientação se ele ainda não estiver no modo paisagem.
Infelizmente, esse mesmo comportamento pode resultar em um loop de tremulação ou efeito letterbox em dispositivos com orientação retrato fixa, o que inclui muitos carros disponíveis hoje.
Para corrigir isso, adicione uma verificação com base nas orientações de tela compatíveis com o dispositivo atual.
- Para simplificar a implementação, adicione o seguinte em
Extensions.kt
:
Extensions.kt
import android.content.Context
import android.content.pm.PackageManager
...
enum class SupportedOrientation {
Landscape,
Portrait,
}
fun Context.supportedOrientations(): List<SupportedOrientation> {
return when (Pair(
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE),
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
)) {
Pair(true, false) -> listOf(SupportedOrientation.Landscape)
Pair(false, true) -> listOf(SupportedOrientation.Portrait)
// For backwards compat, if neither feature is declared, both can be assumed to be supported
else -> listOf(SupportedOrientation.Landscape, SupportedOrientation.Portrait)
}
}
- Em seguida, guarde a chamada para definir a orientação solicitada. Como os apps podem enfrentar problemas semelhantes no modo de várias janelas em dispositivos móveis, você também pode incluir uma verificação para não definir dinamicamente a orientação nesse caso.
PlayerScreen.kt
import com.example.android.cars.roadreels.SupportedOrientation
import com.example.android.cars.roadreels.supportedOrientations
...
LaunchedEffect(Unit) {
...
// Only automatically set the orientation to landscape if the device supports landscape.
// On devices that are portrait only, the activity may enter a compat mode and won't get to
// use the full window available if so. The same applies if the app's window is portrait
// in multi-window mode.
if (context.supportedOrientations().contains(SupportedOrientation.Landscape)
&& !context.isInMultiWindowMode
) {
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
...
}
A tela do player entra em um loop de tremulação no emulador Polestar 2 antes de adicionar a verificação (quando a atividade não processa mudanças de configuração de | A tela do player tem efeito letterbox no emulador Polestar 2 antes da adição da verificação (quando a atividade processa as mudanças de configuração de | A tela do player não tem efeito letterbox no emulador Polestar 2 após a adição da verificação. |
Como esse é o único local no app que define a orientação da tela, o aplicativo agora evita o efeito letterbox. No seu app, verifique se há atributos screenOrientation
ou chamadas setRequestedOrientation
apenas para orientações paisagem ou retrato (incluindo sensor, inversão e variantes do usuário de cada um) e remova ou proteja conforme necessário para limitar o efeito letterbox. Se quiser mais detalhes, consulte o Modo de compatibilidade do dispositivo.
Adaptar à capacidade de controle da barra de sistema
Embora a mudança anterior garanta que o app não entre em um loop de tremulação ou com efeito letterbox, ela também expõe outra suposição que está corrompida, ou seja, que as barras de sistema podem ficar sempre ocultas. Como os usuários têm necessidades diferentes ao usar o carro (em comparação com o uso de smartphones ou tablets), os OEMs têm a opção de impedir que os apps ocultem as barras de sistema para garantir que os controles do veículo, como os de temperatura, estejam sempre acessíveis na tela.
Como resultado, é possível que os apps sejam renderizados atrás das barras de sistema quando estiverem no modo imersivo e presumam que as barras estejam ocultas. Você pode perceber isso na etapa anterior, já que os controles das partes de cima e de baixo do player não ficam mais visíveis quando o app não está com efeito letterbox. Nesse caso específico, o app não é mais navegável, porque o botão para fechar o player está oculto e a funcionalidade dele impedida, já que a barra de busca não pode ser usada.
A correção mais fácil seria aplicar o padding dos insets de janela systemBars
ao player da seguinte maneira:
PlayerScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemBars)
) {
PlayerView(...)
PlayerControls(...)
}
No entanto, essa solução não é ideal, porque faz com que os elementos da interface pulem à medida que as barras de sistema se movimentam.
Para melhorar a experiência do usuário, atualize o app para acompanhar quais encartes podem ser controlados e aplique o padding apenas aos que não podem.
- Como outras telas no app podem ter interesse em controlar os encartes de janela, faz sentido transmitir os encartes controláveis como um
CompositionLocal
. Crie um novo arquivo,LocalControllableInsets.kt
, no pacotecom.example.android.cars.roadreels
e adicione o código abaixo:
LocalControllableInsets.kt
import androidx.compose.runtime.compositionLocalOf
// Assume that no insets can be controlled by default
const val DEFAULT_CONTROLLABLE_INSETS = 0
val LocalControllableInsets = compositionLocalOf { DEFAULT_CONTROLLABLE_INSETS }
- Configure um
OnControllableInsetsChangedListener
para detectar mudanças.
MainActivity.kt
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener
...
class MainActivity : ComponentActivity() {
private lateinit var onControllableInsetsChangedListener: OnControllableInsetsChangedListener
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
var controllableInsetsTypeMask by remember { mutableIntStateOf(DEFAULT_CONTROLLABLE_INSETS) }
onControllableInsetsChangedListener =
OnControllableInsetsChangedListener { _, typeMask ->
if (controllableInsetsTypeMask != typeMask) {
controllableInsetsTypeMask = typeMask
}
}
WindowCompat.getInsetsController(window, window.decorView)
.addOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
}
override fun onDestroy() {
super.onDestroy()
WindowCompat.getInsetsController(window, window.decorView)
.removeOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
}
}
- Adicione um
CompositionLocalProvider
de nível superior que contenha o tema e os elementos combináveis do app e que vincule valores aLocalControllableInsets
.
MainActivity.kt
import androidx.compose.runtime.CompositionLocalProvider
...
CompositionLocalProvider(LocalControllableInsets provides controllableInsetsTypeMask) {
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
- No player, leia o valor atual e use-o para determinar os encartes que serão usados para o padding.
PlayerScreen.kt
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.union
import androidx.compose.ui.unit.dp
import com.example.android.cars.roadreels.LocalControllableInsets
...
val controllableInsetsTypeMask = LocalControllableInsets.current
// When the system bars can be hidden, ignore them when applying padding to the player and
// controls so they don't jump around as the system bars disappear. If they can't be hidden
// include them so nothing renders behind the system bars
var windowInsetsForPadding = WindowInsets(0.dp)
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.statusBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.statusBars)
}
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.navigationBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.navigationBars)
}
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(windowInsetsForPadding)
) {
PlayerView(...)
PlayerControls(...)
}
O conteúdo não pula quando as barras de sistema podem ser ocultadas | O conteúdo permanece visível quando as barras de sistema não podem ser ocultadas |
Muito melhor! O conteúdo não pula e, ao mesmo tempo, os controles ficam totalmente visíveis, mesmo em carros em que as barras de sistema não podem ser controladas.
7. Satisfazer os requisitos de qualidade do Android Automotive OS: distração do motorista
Por fim, há uma grande diferença entre carros e outros formatos: eles são usados para dirigir. Por isso, é muito importante limitar as distrações ao dirigir. Todos os apps para carros estacionados do Android Automotive OS precisam pausar a reprodução quando o veículo começa a ser dirigido. Uma sobreposição do sistema aparece quando a direção começa e, por sua vez, o evento de ciclo de vida onPause
é chamado para o app que está sendo sobreposto. É durante essa chamada que os apps precisam pausar a reprodução.
Simular a direção
Navegue até a visualização do player no emulador e comece a reproduzir o conteúdo. Depois, siga as etapas para simular a direção e observe que, embora a interface do app esteja obscurecida pelo sistema, a reprodução não é pausada. Isso viola a diretriz de qualidade de apps para carros DD-2.
Pausar a reprodução quando o carro começa a ser dirigido
- Adicione uma dependência ao artefato
androidx.lifecycle:lifecycle-runtime-compose
, que contém oLifecycleEventEffect
, que ajuda a executar o código em eventos de ciclo de vida.
libs.version.toml
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
Build.gradle.kts (módulo :app)
implementation(libs.androidx.lifecycle.runtime.compose)
- Depois de sincronizar o projeto para baixar a dependência, adicione um
LifecycleEventEffect
que seja executado no eventoON_PAUSE
para pausar a reprodução.
PlayerScreen.kt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
...
@Composable
fun PlayerScreen(...) {
...
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
player.pause()
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
player.play()
}
...
}
Com a correção implementada, siga as mesmas etapas anteriores para simular a direção durante a reprodução ativa e observe que a reprodução para, satisfazendo o requisito DD-2.
8. Testar o app no emulador de tela distante
Uma nova configuração que está começando a aparecer nos carros é a de duas telas, com uma tela principal no console central e uma tela secundária no painel perto do para-brisa. Os apps podem ser movidos da tela central para a secundária e vice-versa para oferecer mais opções aos motoristas e passageiros.
Instalar a imagem da tela distante do Automotive
- 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 a imagem do emulador Automotive Distant Display with Google APIs para a arquitetura do seu computador (x86/ARM).
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 o perfil de hardware do pacote Automotive Distant Display na lista e clique em Next.
- Na próxima página, selecione a imagem do sistema da etapa anterior. Clique em Next e selecione qualquer opção avançada que você queira antes de criar o AVD. Para isso, clique em Finish.
Executar o app
Execute o app no emulador recém-criado usando a configuração de execução do app
atual. Siga as instruções em Usar o emulador de tela distante para mover o app de e para a tela distante. Tente mover o app quando ele estiver na tela principal/de detalhes e na tela do player, tentando interagir com o app nas duas telas.
9. Melhorar a experiência no app na tela distante
Ao usar o app na tela distante, você pode ter notado duas coisas:
- A reprodução é reiniciada quando o app é movido para a tela distante e longe dela
- Não é possível interagir com o app enquanto ele está na tela distante, o que inclui mudar o estado de reprodução.
Melhorar a continuidade do app
O problema em que a reprodução é reiniciada é causado pela atividade recriada devido a uma mudança de configuração. Como o app é escrito usando o Compose e a configuração que está mudando está relacionada ao tamanho, é fácil permitir que o Compose processe as mudanças de configuração para você restringindo a recriação de atividades para mudanças de configuração baseadas em tamanho. Isso facilita a transição entre as telas, sem interrupção na reprodução ou atualização devido à recriação de atividades.
AndroidManifest.xml
<activity
android:name="com.example.android.cars.roadreels.MainActivity"
...
android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout|density">
...
</activity>
Implementar controles de mídia
Para corrigir o problema em que o app não pode ser controlado enquanto está na tela distante, implemente MediaSession
. As sessões de mídia oferecem uma maneira universal de interagir com um player de áudio ou vídeo. Para mais informações, consulte Controlar e anunciar a reprodução usando uma MediaSession.
- Adicione uma dependência ao artefato
androidx.media3:media3-session
.
libs.version.toml
androidx-media3-mediasession = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
build.gradle.kts (módulo :app)
implementation(libs.androidx.media3.mediasession)
- Crie uma
MediaSession
usando o builder correspondente.
PlayerScreen.kt
import androidx.media3.session.MediaSession
@Composable
fun PlayerScreen(...) {
...
val mediaSession = remember(context, player) {
MediaSession.Builder(context, player).build()
}
...
}
- Em seguida, adicione outra linha ao bloco
onDispose
doDisposableEffect
no elemento combinávelPlayer
para liberar aMediaSession
quando oPlayer
sair da árvore de composição.
PlayerScreen.kt
DisposableEffect(Unit) {
onDispose {
mediaSession.release()
player.release()
...
}
}
- Por fim, na tela do player, você pode testar os controles de mídia usando o comando
adb shell cmd media_session dispatch
.
# To play content
adb shell cmd media_session dispatch play
# To pause content
adb shell cmd media_session dispatch pause
# To toggle the playing state
adb shell cmd media_session dispatch play-pause
Com isso, o app funciona muito melhor em carros com telas distantes. Mais do que isso, ele também funciona melhor em outros formatos. Em dispositivos que permitem girar a tela ou que os usuários redimensionem a janela de um app, agora o app também se adapta perfeitamente a essas situações.
Além disso, graças à integração da sessão de mídia, a reprodução do app pode ser controlada não apenas por controles de hardware e software em carros, mas também por outras fontes, como uma consulta do Google Assistente ou um botão de pausa em fones de ouvido, dando aos usuários mais opções para controlar o app em vários formatos.
10. Testar o app em diferentes configurações do sistema
Com o app funcionando bem na tela principal e na tela distante, a última coisa a verificar é como ele processa diferentes configurações da barra de sistema e cortes da tela. Conforme descrito em Trabalhar com encartes de janela e cortes da tela, os dispositivos Android Automotive OS podem apresentar configurações que quebram as suposições geralmente aplicáveis a formatos de dispositivos móveis.
Nesta seção, você vai baixar um emulador que pode ser configurado no tempo de execução, configurar o emulador para ter uma barra de sistema à esquerda e testar o app nessa configuração.
Instalar a imagem do Android Automotive com APIs do Google
- 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 a imagem do emulador do Android Automotive com APIs do Google na API 33 para a arquitetura do seu computador (x86/ARM).
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 o perfil de hardware do pacote Automotive (1080p landscape) na lista e clique em Next.
- Na próxima página, selecione a imagem do sistema da etapa anterior. Clique em Next e selecione qualquer opção avançada que você queira antes de criar o AVD. Para isso, clique em Finish.
Configurar uma barra de sistema lateral
Conforme detalhado em Testar usando o emulador configurável, há várias opções para emular diferentes configurações de sistema presentes nos carros.
Para os fins deste codelab, o com.android.systemui.rro.left
pode ser usado para testar uma configuração diferente da barra de sistema. Para ativá-lo, use o seguinte comando:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
Como o app usa o modificador systemBars
como contentWindowInsets
no Scaffold
, o conteúdo já está sendo renderizado em uma área segura das barras de sistema. Para saber o que aconteceria se o app presumisse que as barras de sistema só apareciam na parte de cima e de baixo da tela, mude esse parâmetro para o seguinte:
RoadReelsApp.kt
contentWindowInsets = if (route?.equals(Screen.Player.name) == true) WindowInsets(0.dp) else WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
Opa! A tela de lista e detalhes é renderizada atrás da barra de sistema. Graças ao trabalho anterior, a tela do player estaria OK, mesmo que as barras de sistema não fossem controláveis.
Antes de passar para a próxima seção, reverta a mudança que você acabou de fazer no parâmetro windowContentPadding
.
11. Trabalhar com cortes da tela
Por fim, alguns carros têm telas com cortes muito diferentes dos encontrados em dispositivos móveis. Em vez dos entalhes ou recortes da câmera com orifício, alguns veículos com Android Automotive OS têm telas curvas que tornam a imagem não retangular.
Para conferir como o app se comporta quando esse recorte está presente, primeiro ative o corte da tela usando o seguinte comando:
adb shell cmd overlay enable --user 0 com.android.internal.display.cutout.emulation.free_form
Para testar o comportamento do app, ative também a barra de sistema esquerda usada na última seção, se ela ainda não estiver ativada:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
No momento, o app não é renderizado no corte da tela. É difícil identificar o formato exato do corte, mas ele vai ficar claro na próxima etapa. Isso é perfeitamente aceitável e uma experiência melhor do que a de um app renderizado em cortes, mas que não se adapta a eles com cuidado.
Renderizar no corte da tela
Para oferecer aos usuários a experiência mais imersiva possível, você pode usar muito mais espaço na tela renderizando o corte da tela.
- Para renderizar no corte da tela, crie um arquivo
integers.xml
para armazenar a substituição específica para carros. Para fazer isso, use o qualificador UI mode com o valor Car Dock. O nome é uma referência de quando apenas o Android Auto existia, mas também é usado pelo Android Automotive OS. Além disso, como oLAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
foi introduzido no Android R, adicione também o qualificador Version do Android com o valor 30. Consulte Usar recursos alternativos para saber mais detalhes.
- No arquivo que você acabou de criar (
res/values-car-v30/integers.xml
), adicione o seguinte:
integers.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="windowLayoutInDisplayCutoutMode">3</integer>
</resources>
O valor inteiro 3
corresponde a LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
e substitui o valor padrão de 0
de res/values/integers.xml
, que corresponde a LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
. Esse valor inteiro já é referenciado em MainActivity.kt
para substituir o modo definido por enableEdgeToEdge()
. Para mais informações sobre esse atributo, consulte a documentação de referência.
Agora, ao executar o app, observe que o conteúdo se estende para o corte e parece muito imersivo. No entanto, a barra de apps na parte de cima e parte do conteúdo ficam parcialmente obscurecidos pelo corte da tela, causando um problema semelhante ao que acontecia quando o app presumia que as barras de sistema só apareciam na parte de cima e de baixo.
Corrigir as barras de apps na parte de cima
Para corrigir as barras de apps na parte de cima, adicione o seguinte parâmetro windowInsets
aos elementos combináveis CenterAlignedTopAppBar
:
RoadReelsApp.kt
import androidx.compose.foundation.layout.safeDrawing
...
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
Como safeDrawing
consiste nos encartes displayCutout
e systemBars
, isso melhora o parâmetro windowInsets
padrão, que usa apenas systemBars
ao posicionar a barra de apps na parte de cima.
Além disso, como a barra de apps superior está posicionada na parte de cima da janela, não inclua o componente de baixo dos encartes safeDrawing
. Isso pode adicionar um padding desnecessário.
Corrigir a tela principal
Uma opção para corrigir o conteúdo nas telas principal e de detalhes é usar safeDrawing
em vez de systemBars
para os contentWindowInsets
do Scaffold
. No entanto, o app parece bem menos imersivo usando essa opção, com o conteúdo sendo cortado abruptamente onde o corte da tela começa - nada melhor do que se o app não fosse renderizado no corte da tela.
Para uma interface do usuário mais imersiva, é possível processar os encartes em cada componente na tela.
- Atualize os
contentWindowInsets
doScaffold
para que sejam constantemente 0 dp (em vez de apenas para aPlayerScreen
). Isso permite que cada tela e/ou componente dentro de uma tela determine o comportamento em relação aos encartes.
RoadReelsApp.kt
Scaffold(
...,
contentWindowInsets = WindowInsets(0.dp)
) { ... }
- Defina o
windowInsetsPadding
dos elementos combináveisText
do cabeçalho da linha para usar os componentes horizontais dos encartessafeDrawing
. O componente superior desses encartes é processado pela barra de apps superior, e o componente inferior será processado mais tarde.
MainScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
LazyColumn(
contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.screen_edge_padding))
) {
items(NUM_ROWS) { rowIndex: Int ->
Text(
"Row $rowIndex",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.padding(
horizontal = dimensionResource(R.dimen.screen_edge_padding),
vertical = dimensionResource(R.dimen.row_header_vertical_padding)
)
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
)
...
}
- Remova o parâmetro
contentPadding
daLazyRow
. Em seguida, no início e no fim de cadaLazyRow
, adicione umSpacer
com a largura do componentesafeDrawing
correspondente para garantir que todas as miniaturas possam ser visualizadas. Use o modificadorwidthIn
para garantir que esses espaçadores tenham pelo menos a mesma largura do padding do conteúdo. Sem esses elementos, os itens no início e no fim da linha podem ser ocultados atrás das barras de sistema e/ou do corte da tela, mesmo quando deslizados completamente para o início/fim da linha.
MainScreen.kt
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsEndWidth
import androidx.compose.foundation.layout.windowInsetsStartWidth
...
LazyRow(
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.list_item_spacing)),
) {
item {
Spacer(
Modifier
.windowInsetsStartWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
items(NUM_ITEMS_PER_ROW) { ... }
item {
Spacer(
Modifier
.windowInsetsEndWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
}
- Por fim, adicione um
Spacer
ao final daLazyColumn
para considerar possíveis barras de sistema ou encartes de corte da tela na parte de baixo da tela. Não é necessário um espaçamento equivalente na parte de cima daLazyColumn
, porque a barra de apps superior os processa. Se o app usar uma barra de apps inferior em vez de uma barra de apps superior, adicione umSpacer
ao início da lista usando o modificadorwindowInsetsTopHeight
. Se o app usar uma barra de apps na parte de cima e na parte de baixo, nenhum espaçador será necessário.
MainScreen.kt
import androidx.compose.foundation.layout.windowInsetsBottomHeight
...
LazyColumn(...){
items(NUM_ROWS) { ... }
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}
Perfeito! As barras de apps superiores estão totalmente visíveis e, ao rolar até o final de uma linha, você pode ver todas as miniaturas na íntegra.
Corrigir a tela de detalhes
A tela de detalhes não está tão ruim, mas o conteúdo ainda está sendo cortado.
Como a tela de detalhes não tem conteúdo rolável, basta adicionar um modificador windowInsetsPadding
à Box
de nível superior para corrigir o problema.
DetailScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = modifier
.padding(dimensionResource(R.dimen.screen_edge_padding))
.windowInsetsPadding(WindowInsets.safeDrawing)
) { ... }
Corrigir a tela do player
Embora a PlayerScreen
já aplique padding a alguns ou todos os encartes da janela da barra de sistema da seção Satisfazer os requisitos de qualidade do Android Automotive OS: navegação, isso não é suficiente para garantir que não seja ocultada agora que o app está renderizando em cortes da tela. Em dispositivos móveis, os cortes da tela quase sempre estão totalmente contidos nas barras de sistema. No entanto, em carros, os cortes da tela podem se estender muito além das barras de sistema, acabando com as suposições.
Para corrigir isso, basta mudar o valor inicial da variável windowInsetsForPadding
de um valor zero para displayCutout
:
PlayerScreen.kt
import androidx.compose.foundation.layout.displayCutout
...
var windowInsetsForPadding = WindowInsets(WindowInsets.displayCutout)
Legal! O app realmente aproveita ao máximo a tela e ainda permanece utilizável.
E, se você executar o app em um dispositivo móvel, ele também será mais imersivo. Os itens de lista são renderizados até as bordas da tela, inclusive atrás da barra de navegação.
12. Parabéns
Você migrou e otimizou seu primeiro app para carros estacionados. Agora é hora de aplicar ao seu app aquilo que você aprendeu.
O que testar
- Substitua alguns valores de recursos de dimensão para aumentar o tamanho dos elementos ao executar em um carro.
- Teste ainda mais configurações do emulador configurável.
- Teste o app usando algumas das imagens do emulador OEM disponíveis.
Leia mais
- Criar apps do Android Automotive OS para carros estacionados
- Criar apps de vídeo para o Android Automotive OS
- Criar jogos para o Android Automotive OS
- Criar navegadores para o Android Automotive OS
- 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. Filtre pela categoria do seu app.