Criar um app de mídia com modelo

Os apps de mídia com base em modelo estão na versão Beta
No momento, qualquer pessoa pode publicar apps de música com modelo nas faixas de teste interno e fechado na Google Play Store. A publicação em faixas abertas e de produção será permitida posteriormente.

Os apps de música que usam os modelos da biblioteca de apps para carros podem personalizar a experiência de navegação e reprodução de mídia, garantindo que ela seja otimizada para telas de carros e minimize as distrações ao dirigir.

Este guia presume que você já tenha um app de música que toca áudio em um smartphone e que ele seja compatível com a arquitetura de app de música do Android. A biblioteca de apps para carros permite substituir a experiência no app por modelos em vez daqueles criados usando a estrutura de dados MediaBrowser de Criar apps de música para carros. Ainda é necessário fornecer um MediaSession para controles de mídia e um MediaBrowserService ou MediaLibraryService, que é usado para recomendações e outras experiências inteligentes.

As experiências de estacionamento para dirigir agora estão disponíveis na versão Beta
Os apps de mídia baseados em modelos agora podem adicionar uma experiência de estacionar para dirigir no Android Automotive OS. Isso permite que o app adicione atividades nativas adaptáveis enquanto o carro está estacionado e faça uma transição perfeita para a experiência de reprodução de mídia com modelo quando o carro está em movimento.

Configurar o manifesto do app

Além das etapas descritas em Como usar a biblioteca de apps Android para carros, os apps de mídia baseados em modelos precisam:

Declarar suporte à categoria no manifesto

Seu app precisa declarar a categoria de app para carros androidx.car.app.category.MEDIA no filtro de intent de CarAppService.

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.MEDIA"/>
      </intent-filter>
    </service>
    ...
<application>

Para ter acesso ao MediaPlaybackTemplate, seu app também precisa declarar a permissão androidx.car.app.MEDIA_TEMPLATES no arquivo de manifesto:

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

Definir o nível mínimo da API Car App

Os apps de música que usam o MediaPlaybackTemplate só são compatíveis com a API CAL 8 e versões mais recentes. Verifique se o Car App API level mínimo está definido como 8.

<application ...>
  ...
  <meta-data
    android:name="androidx.car.app.minCarApiLevel"
    android:value="8"/>
  ...
</application>

Fornecer um ícone de atribuição

Adicione um ícone de atribuição para apps de música criados com a biblioteca de apps para carros.

Declarar compatibilidade com o Android Auto

Verifique se o seguinte está incluído no manifesto do app:

<application>
  ...
  <meta-data android:name="com.google.android.gms.car.application"
      android:resource="@xml/automotive_app_desc"/>
  ...
</application>

Em seguida, adicione a declaração template a automotive_app_desc.xml nos recursos XML. Ele terá a seguinte aparência:

<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android">
 <uses name="media"/>
 <uses name="template"/>
</automotiveApp>

Declarar suporte ao Android Automotive OS

Há duas maneiras de distribuir um app de mídia com a biblioteca de apps Car App no Android Automotive OS: como um único APK ou como dois APKs separados. Se você distribuir um único APK, ele será compatível com veículos que têm o Android Automotive OS com o host da biblioteca de apps Car e, se não tiverem, vai usar um aplicativo MediaBrowserService ou MediaLibraryService, mesmo para versões mais antigas do Android (Android 10 a Android 13). Se você optar por distribuir dois APKs separados, poderá atualizar as novas adições à versão da biblioteca de apps para carros sem medo de afetar a versão MediaBrowserService ou MediaLibraryService do seu app.

Distribuir um único APK

Ao distribuir um único APK para a biblioteca de apps Car App e as versões MediaBrowserService ou MediaLibraryService do seu app, é fundamental definir o android.software.car.templates_host.media como android:required="false".

<uses-feature android:name="android.software.car.templates_host.media" android:required="false"/>

Em seguida, siga as diretrizes da biblioteca de apps para carros para o AAOS e introduza uma CarAppActivity inicializável (ou atividade de trampolim). É necessário definir a atividade como android:enabled="false" no manifesto. Em seguida, adicione uma tag de metadados à declaração MediaBrowserService indicando o componente CarAppActivity como substituto. Confira o exemplo de manifesto a seguir:

<service android:name=".media.MyMediaService"
    android:exported="true"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
    </intent-filter>

    <!-- Link to Car App Library Activity -->
    <meta-data
        android:name="androidx.car.app.media.CalMediaActivityComponent"
        android:value="com.example.mediaapp.LaunchableTrampoline"/>
</service>

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false"> <!-- Set to false -->

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Distribuição do Google Play

O APK com a biblioteca de apps para carros e MediaBrowserService ou MediaLibraryService precisa ser ativado com um código de versão mais recente e minSdk direcionado ao Android 14 (34).

Distribuição com dois APKs

Para distribuir dois APKs separados, um usando a biblioteca de apps Car e outro usando MediaBrowserService ou MediaLibraryService, siga estas etapas para garantir que os recursos corretos do veículo sejam segmentados corretamente.

Ao criar um APK separado para a versão da biblioteca de apps para carros do seu app, defina android.software.car.templates_host.media como android:required=true. Isso garante que o app seja distribuído apenas em builds do Android Automotive OS certificados com suporte ao host da biblioteca de apps Car App.

<uses-feature android:name="android.software.car.templates_host.media" android:required="true"/>

Além de usar android.software.car.templates_host.media e definir como android:required=true, siga estas etapas para ativar o Android Automotive OS na atividade da biblioteca de apps para carros que pode ser iniciada.

Distribuição do Google Play

O APK que usa a biblioteca de apps Car precisa ser distribuído na faixa dedicada ao Automotive OS.

Oferecer compatibilidade com comandos de voz

Ative a voz no seu app para que os usuários possam realizar ações comuns por comando de voz. Consulte Suporte para comandos de voz para mídia para ver instruções de implementação mais detalhadas. Com um app de música baseado em modelo, se você receber um comando de voz, não precisará atualizar o MediaBrowserService ou o MediaLibraryService com os resultados da pesquisa. Em vez disso, adicione uma ação ao modelo de reprodução de mídia para permitir que o usuário encontre mais conteúdo com base nessa reprodução ou consulta de pesquisa. O suporte a comandos de voz é necessário para atender à diretriz de qualidade VC-1.

Criar seu modelo de reprodução

O MediaPlaybackTemplate mostra informações de reprodução de mídia no app de mídia da biblioteca de apps para carros. Esse modelo permite definir um cabeçalho com um título e ações personalizáveis, enquanto as informações de mídia e os controles de mídia são preenchidos pelo host com base no estado do MediaSession do app.

Um player de música mostra Sounds of Spring, de Summer Fielding, com um
     retrato quadrado de uma mulher tocando violão.

Figura 1: MediaPlaybackTemplate com uma ação de cabeçalho para abrir a fila na parte de cima.

Este exemplo de código mostra como criar um modelo de reprodução que define uma ação de cabeçalho que permite ao usuário navegar até uma tela com a fila de músicas.

val playbackTemplate = MediaPlaybackTemplate.Builder()
      .setHeader(
        Header.Builder()
          .setStartHeaderAction(Action.BACK)
          .addEndHeaderAction(
                Action.Builder()
                  .setTitle(model.context.getString(R.string.queue_button_title))
                  .setIcon(
                    CarIcon.Builder(
                        IconCompat.createWithResource(
                          model.context,
                          R.drawable.gs_queue_music_vd_theme_24,
                        ))
                      .build())
                  .setOnClickListener(showQueueScreen())
                  .build())
          .setTitle(model.context.getString(R.string.media_playback_view_title))
          .build())
      .build()

Ao usar MediaPlaybackTemplate, registre um token MediaSession usando o MediaPlaybackManager no seu CarAppService. Se isso não for feito, um erro será exibido quando um MediaPlaybackTemplate for enviado ao host.

import androidx.car.app.media.MediaPlaybackManager
…

override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return object : Session() {
        …

        init {
          lifecycle.addObserver(
            LifecycleEventObserver { _, event ->
              if (event == ON_CREATE) {
                val token = ... // MediaSessionCompat.Token
                (carContext.getCarService(CarContext.MEDIA_PLAYBACK_SERVICE) as MediaPlaybackManager)
                  .registerMediaPlaybackToken(token)
              }
              ...
            }
          )
        }
    }
}

.registerMediaPlaybackToken é necessário para expor informações e controles de reprodução de mídia ao Android Auto. Isso também é importante para que o host crie notificações específicas de mídia.

Para apps que usam a biblioteca Media3, que usam um PlatformToken em vez de um MediaSessionCompat.Token padrão, é necessário implementar um SessionCommand personalizado no MediaLibrarySession.Callback que retorna o token de plataforma subjacente da sessão: session.platformToken. No CarAppService, envie este comando personalizado para a sessão. Depois de receber o token da plataforma, converta-o usando MediaSessionCompat.Token.fromToken(platformToken) e transmita esse token de compatibilidade para a biblioteca de apps do carro em .registerMediaPlaybackToken().

Organizar mídia usando modelos

Para organizar mídias para navegação, como músicas ou álbuns, recomendamos o uso do SectionedItemTemplate, que permite usar o GridSection e o RowSection juntos para criar layouts que misturam listas de imagens e itens de texto.

A interface de um app de música mostra músicas e álbuns tocados recentemente,
     incluindo duas linhas verticais e três capas de álbuns horizontais.

Figura 2: uma SectionedItemTemplate que contém uma RowSection seguida por uma GridSection

Como usar SectionedItemTemplate dentro de um TabTemplate

Uma maneira conveniente de categorizar mídia no app é usar o SectionedItemTemplate dentro de um TabTemplate.

val template =
      SectionedItemTemplate.Builder()...build();
val tabTemplate =
      TabTemplate.Builder(tabCallback)
          .setTabContents(TabContents.Builder(template).build)
          .setHeaderAction(Action.APP_ICON)
          …
          .build();

Componentes e recursos da biblioteca de apps para carros 1.9

A versão 1.9 da API Car App Library apresenta componentes personalizados para recursos de navegação exclusivos, como ícones, barras de progresso, itens condensados, cabeçalho interativo e expandido, seções de destaque e banners.

A interface de um app de música mostra músicas e álbuns tocados recentemente,
     incluindo duas linhas verticais e três capas de álbuns horizontais.

Figura 3: um SectionedItemTemplate que contém Chips, Condensed Items, um Interactive Header, Grid Items e um Minimized Control Panel

A interface de um app de música mostra músicas e álbuns tocados recentemente,
     incluindo duas linhas verticais e três capas de álbuns horizontais.

Figura 4: duas telas de navegação de mídia com Expanded Header, Spotlight Sections e Progress Bars

Para saber mais sobre como projetar a interface do usuário do seu app de música usando esses modelos, consulte Apps de música.

Ao navegar pela mídia, é importante que o usuário possa acessar rapidamente o MediaPlaybackTemplate com o mínimo de distração.Para atender ao requisito de qualidade MFT-1, o app precisa ter uma maneira de acessar o MediaPlaybackTemplate em todas as telas de navegação de mídia.

Se você estiver usando SectionedItemTemplate, adicione um botão de ação que direcione à tela de reprodução de mídia. Use a ação padrão Action.MEDIA_PLAYBACK da biblioteca de apps para carros. Um app de música vai mostrar essa ação como um painel de controle minimizado, que é necessário para atender ao requisito de qualidade MFT-1 se você estiver usando a API 1.9 ou mais recente da biblioteca de apps Car App. Para outros modelos, uma ação de cabeçalho é outra maneira de fazer isso.

Processar intents de reprodução de mídia do sistema

É necessário direcionar o usuário para o MediaPlaybackTemplate quando um aplicativo é iniciado em uma superfície de mídia do sistema, como um card de mídia. Exigimos que os aplicativos de mídia processem esse Intent Action para oferecer uma experiência perfeita aos usuários.

Adicione a ação androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK ao filtro de intent do componente da biblioteca de apps Car App (CarAppActivity ou o trampoline Activity).

Verifique se a atividade usa um launchMode de singleTask ou singleTop para que onNewIntent() seja invocado.

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false">

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

Na classe Session, substitua onNewIntent() para analisar a intent recebida. Se a ação de intent recebida corresponder a SHOW_MEDIA_PLAYBACK, direcione o usuário para a tela "Tocando agora".

@Override
public void onNewIntent(@NonNull Intent intent) {
    super.onNewIntent(intent);
    if (SHOW_MEDIA_PLAYBACK.equals(intent.getAction())) {
        ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
        // Avoid redundant navigation if already on the playing screen
        if (screenManager.getTop() instanceof MyMediaPlayScreen) {
            return;
        }
        screenManager.push(MyMediaPlayScreen.createScreenFromPlaying(
                getCarContext(), mMediaSessionController));
    }
}

Se você estiver usando uma atividade de trampolim, verifique a ação da intent em onCreate(). Transmita essa ação para a intent de criação de CarAppActivity antes de chamar finish().

public class LaunchableTrampoline extends AppCompatActivity {
    private static final String SHOW_MEDIA_PLAYBACK = "androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent receivedIntent = getIntent();
        String action;

        if (SHOW_MEDIA_PLAYBACK.equals(receivedIntent.getAction())) {
            action = SHOW_MEDIA_PLAYBACK;
        } else {
            action = Intent.ACTION_MAIN;
        }

        Intent intent = new Intent(action);
        intent.setClassName(getPackageName(), "androidx.car.app.activity.CarAppActivity");
        startActivity(intent);
        finish();
    }
}