Créer des applications multimédias pour voitures

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Android Auto et Android Automotive OS vous permettent de proposer le contenu de votre application multimédia aux utilisateurs dans leur voiture. Une application multimédia pour voiture doit fournir un service de navigateur multimédia pour qu'Android Auto et Android Automotive OS (ou une autre application proposant un navigateur multimédia) puissent découvrir et afficher votre contenu.

Dans ce guide, nous partons du principe que vous disposez déjà d'une application multimédia qui lit du contenu audio sur un téléphone et que cette application est conforme à l'architecture des applications multimédias Android.

Ce guide décrit les composants requis d'un MediaBrowserService et d'une MediaSession dont votre application a besoin pour fonctionner avec Android Auto ou Android Automotive OS. Une fois que vous avez terminé l'infrastructure multimédia principale, vous pouvez ajouter la compatibilité avec Android Auto et Android Automotive OS à votre application multimédia.

Avant de commencer

  1. Consultez la documentation de l'API multimédia Android.
  2. Consultez les consignes de conception des applications Android Automotive OS et les consignes de conception des applications Android Auto.
  3. Passez en revue les termes et concepts clés abordés dans cette section.

Termes et concepts clés

Service de navigateur multimédia
Service Android implémenté par votre application multimédia et conforme à l'API MediaBrowserServiceCompat. Votre application utilise ce service pour présenter son contenu.
Navigateur multimédia
API utilisée par les applications multimédias pour découvrir les services de navigateur multimédia et en afficher le contenu. Android Auto et Android Automotive OS utilisent un navigateur multimédia pour trouver le service de navigateur multimédia de votre application.
Élément multimédia

Le navigateur multimédia organise son contenu dans une arborescence d'objets MediaItem. Un élément multimédia peut avoir l'un des indicateurs suivants ou les deux :

  • Lisible : indique que l'élément est une feuille de l'arborescence de contenu. L'élément représente un seul flux audio, comme une chanson d'un album, un chapitre d'un livre audio ou un épisode d'un podcast.
  • Consultable : indique que l'élément est un nœud de l'arborescence de contenu et qu'il possède des éléments enfants. Par exemple, l'élément représente un album, et ses enfants sont les chansons qui le constituent.

Un élément multimédia qui est à la fois consultable et lisible se comporte comme une playlist. Vous pouvez sélectionner l'élément proprement dit pour lire tous ses éléments enfants ou vous pouvez parcourir ses éléments enfants.

Optimisé pour les véhicules

Activité d'une application Android Automotive OS conforme aux consignes de conception des applications Android Automotive OS. L'interface de ces activités n'est pas dessinée par Android Automotive OS. Vous devez donc vous assurer que votre application respecte les consignes de conception. En règle générale, cela inclut des polices et des éléments tactiles de plus grande taille, la compatibilité avec les modes Jour et Nuit, ainsi que des rapports de contraste plus élevés.

Les interfaces utilisateur optimisées pour les véhicules ne peuvent être affichées que lorsque les restrictions liées à l'expérience utilisateur du véhicule (CUXR) ne sont pas en vigueur. En effet, elles peuvent nécessiter une plus grande attention ou interaction de la part de l'utilisateur. Ces restrictions ne s'appliquent pas lorsque la voiture est à l'arrêt, mais elles s'appliquent systématiquement lorsque la voiture est en mouvement.

Il n'est pas nécessaire de concevoir des activités pour Android Auto, car cette fonctionnalité dessine sa propre interface optimisée pour les véhicules à l'aide des informations provenant de votre service de navigateur multimédia.

Configurer les fichiers manifestes de votre application

Avant de pouvoir créer votre service de navigateur multimédia, vous devez configurer les fichiers manifestes de votre application.

Déclarer votre service de navigateur multimédia

Android Auto et Android Automotive OS se connectent à votre application via votre service de navigateur multimédia afin de parcourir les éléments multimédias. Vous déclarez votre service de navigateur multimédia dans le fichier manifeste pour autoriser Android Auto et Android Automotive OS à découvrir le service et à se connecter à votre application.

L'extrait de code suivant montre comment déclarer le service de navigateur multimédia dans le fichier manifeste. Vous devez inclure ce code dans le fichier manifeste de votre module Android Automotive OS ainsi que dans celui de votre application pour téléphone.

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

Spécifier des icônes d'application

Vous devez spécifier les icônes d'applications qu'Android Auto et Android Automotive OS peuvent utiliser pour représenter votre application dans l'UI du système. Deux types d'icônes sont obligatoires :

  • Icône de lanceur
  • Icône d'attribution

Icône de lanceur

L'icône de lanceur représente votre application dans l'UI du système, par exemple dans le Lanceur d'applications et la barre d'icônes. Vous pouvez spécifier une icône pour représenter votre application à l'aide de la déclaration de fichier manifeste suivante, qui utilisera la même icône que votre application mobile :

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

Pour utiliser une icône différente de celle de l'application mobile, ajoutez une propriété d'icône au fichier manifeste du service de navigateur multimédia.

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

Icône d'attribution

Icône d'attribution sur la fiche multimédia

L'icône d'attribution est utilisée là où le contenu multimédia est prioritaire, par exemple sur les fiches multimédias. Pensez à réutiliser la petite icône associée aux notifications. Cette icône doit être monochrome. Vous pouvez spécifier une icône représentant votre application à l'aide de la déclaration de fichier manifeste suivante :

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

Créer votre service de navigateur multimédia

Pour créer un service de navigateur multimédia, développez la classe MediaBrowserServiceCompat. Android Auto et Android Automotive OS peuvent alors utiliser votre service pour effectuer les opérations suivantes :

  • Parcourir la hiérarchie de contenu de votre application afin de présenter un menu à l'utilisateur
  • Obtenir le jeton de l'objet MediaSessionCompat de votre application afin de contrôler la lecture audio

Workflow du service de navigateur multimédia

Cette section explique comment Android Automotive OS et Android Auto interagissent avec votre service de navigateur multimédia au cours d'un workflow utilisateur standard.

  1. Un utilisateur lance votre application sur Android Automotive OS ou sur Android Auto.
  2. Android Automotive OS ou Android Auto contacte le service de navigateur multimédia de votre application à l'aide de la méthode onCreate(). Dans l'implémentation de la méthode onCreate(), vous devez créer et enregistrer un objet MediaSessionCompat ainsi que son objet de rappel.
  3. Android Automotive OS ou Android Auto appelle la méthode onGetRoot() de votre service pour obtenir l'élément multimédia racine de votre hiérarchie de contenu. L'élément multimédia racine n'est pas affiché, mais utilisé pour récupérer plus de contenu de votre application.
  4. Android Automotive OS ou Android Auto appelle la méthode onLoadChildren() de votre service pour obtenir les enfants de l'élément multimédia racine. Android Automotive OS et Android Auto affichent ces éléments multimédias en tant que premier niveau d'éléments de contenu. Consultez la section Structurer le menu racine de cette page pour en savoir plus sur les attentes du système à ce niveau.
  5. Si l'utilisateur sélectionne un élément multimédia consultable, la méthode onLoadChildren() de votre service est de nouveau appelée pour récupérer les enfants de l'élément de menu sélectionné.
  6. Si l'utilisateur sélectionne un élément multimédia lisible, Android Automotive OS ou Android Auto appelle la méthode de rappel de la session multimédia appropriée pour effectuer cette action.
  7. Si votre application l'y autorise, l'utilisateur peut également effectuer des recherches dans votre contenu. Dans ce cas, Android Automotive OS ou Android Auto appelle la méthode onSearch() de votre service.

Créer une hiérarchie de contenu

Android Auto et Android Automotive OS appellent le service de navigateur multimédia de votre application pour connaître le contenu disponible. Pour cela, vous devez implémenter deux méthodes dans votre service de navigateur multimédia : onGetRoot() et onLoadChildren().

Implémenter onGetRoot

La méthode onGetRoot() de votre service renvoie des informations sur le nœud racine de votre hiérarchie de contenu. Android Auto et Android Automotive OS utilisent ce nœud racine pour demander le reste du contenu à l'aide de la méthode onLoadChildren().

L'extrait de code suivant illustre une implémentation simple de la méthode onGetRoot() :

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? =
    // Verify that the specified package is allowed to access your
    // content! You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        null
    } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // Verify that the specified package is allowed to access your
    // content! You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        return null;
    }

    return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

Pour obtenir un exemple plus détaillé de cette méthode, consultez la méthode onGetRoot() dans l'application exemple Universal Android Music Player sur GitHub.

Ajouter la validation de package pour onGetRoot()

Lorsqu'un appel est effectué vers la méthode onGetRoot() de votre service, le package à l'origine de l'appel transmet des informations d'identification à votre service. Votre service peut utiliser ces informations pour déterminer si ce package peut accéder à votre contenu. Par exemple, vous pouvez limiter l'accès au contenu de votre application à une liste de packages approuvés en comparant clientPackageName à votre liste d'autorisation et en vérifiant le certificat utilisé pour la signature de l'APK du package. Si le package ne peut pas être vérifié, renvoyez null pour refuser l'accès à votre contenu.

Pour que les applications système (comme Android Auto et Android Automotive OS) puissent accéder à votre contenu, votre service doit toujours renvoyer une valeur BrowserRoot non nulle lorsque ces applications appellent la méthode onGetRoot(). La signature de l'application système Android Automotive OS peut varier selon la marque et le modèle de la voiture. Vous devez donc autoriser les connexions provenant de toutes les applications système pour assurer une bonne compatibilité avec Android Automotive OS.

L'extrait de code suivant montre comment votre service peut vérifier que le package à l'origine de l'appel est bien une application système :

fun isKnownCaller(
    callingPackage: String,
    callingUid: Int
): Boolean {
    ...
    val isCallerKnown = when {
       // If the system is making the call, allow it.
       callingUid == Process.SYSTEM_UID -> true
       // If the app was signed by the same certificate as the platform
       // itself, also allow it.
       callerSignature == platformSignature -> true
       // ... more cases
    }
    return isCallerKnown
}

Cet extrait de code provient de la classe PackageValidator dans l'application exemple Universal Android Music Player sur GitHub. Reportez-vous à cette classe pour obtenir un exemple plus détaillé d'implémentation de la validation de package pour la méthode onGetRoot() de votre service.

En plus d'autoriser les applications système, vous devez permettre à l'Assistant Google de se connecter à votre MediaBrowserService. Notez que l'Assistant Google utilise des noms de package distincts pour le téléphone (ce qui inclut Android Auto) et pour Android Automotive OS.

Implémenter onLoadChildren()

Après avoir reçu votre objet de nœud racine, Android Auto et Android Automotive OS créent un menu de niveau supérieur en appelant onLoadChildren() sur cet objet pour obtenir ses enfants. Les applications clientes créent des sous-menus en appelant cette même méthode à l'aide d'objets de nœuds enfants.

Chaque nœud de votre hiérarchie de contenu est représenté par un objet MediaBrowserCompat.MediaItem. Chacun de ces éléments multimédias est identifié par une chaîne d'identifiant unique. Les applications clientes traitent ces chaînes d'identifiant comme des jetons opaques. Lorsqu'une application cliente souhaite accéder à un sous-menu ou lire un élément multimédia, elle transmet le jeton. Votre application est chargée d'associer le jeton à l'élément multimédia approprié.

Remarque : Android Auto et Android Automotive OS imposent des limites strictes en ce qui concerne le nombre d'éléments multimédias pouvant être affichés à chaque niveau du menu. Ces limites réduisent les distractions pour les conducteurs et leur permettent d'utiliser votre application à l'aide de commandes vocales. Pour en savoir plus, consultez Informations sur la navigation dans le contenu et Vues de navigation.

L'extrait de code suivant illustre une implémentation simple de la méthode onLoadChildren() :

Kotlin

override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaBrowserCompat.MediaItem>>
) {
    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

        // build the MediaItem objects for the top level,
        // and put them in the mediaItems list
    } else {

        // examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list
    }
    result.sendResult(mediaItems)
}

Java

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaBrowserCompat.MediaItem>> result) {

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // build the MediaItem objects for the top level,
        // and put them in the mediaItems list
    } else {

        // examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list
    }
    result.sendResult(mediaItems);
}

Pour obtenir un exemple complet de cette méthode, consultez la méthode onLoadChildren() dans l'application exemple Universal Android Music Player sur GitHub.

Structurer le menu racine

Figure 1. Contenu racine affiché sous la forme d'onglets de navigation

Android Auto et Android Automotive OS présentent des contraintes spécifiques concernant la structure du menu racine. Elles sont transmises à MediaBrowserService via des suggestions (hints) de racine qui peuvent être lues au moyen de l'argument Bundle transmis à onGetRoot(). En suivant ces suggestions, le système peut afficher le contenu racine de manière optimale sous la forme d'onglets de navigation. Si vous ne suivez pas ces suggestions, il se peut qu'une partie du contenu racine soit supprimée ou que le système la rende moins visible. Deux suggestions sont envoyées :

  1. Limite du nombre d'enfants racines : dans la plupart des cas, ce nombre est égal à quatre. Cela signifie qu'il n'est pas possible d'afficher plus de quatre onglets.
  2. Indicateurs acceptés sur les enfants racines : cette valeur est généralement MediaItem#FLAG_BROWSABLE. Cela signifie que seuls les éléments consultables peuvent être affichés sous la forme d'onglets, ce qui n'est pas le cas des éléments lisibles.

Utilisez le code suivant pour lire les suggestions de racine pertinentes :

Kotlin

import androidx.media.utils.MediaConstants

// Later, in your MediaBrowserServiceCompat
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

  val maximumRootChildLimit = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
      /* defaultValue= */ 4)
  val supportedRootChildFlags = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
      /* defaultValue= */ MediaItem.FLAG_BROWSABLE)

  // rest of method..
}

Java

import androidx.media.utils.MediaConstants;

// Later, in your MediaBrowserServiceCompat
@Override
public BrowserRoot onGetRoot(
    String clientPackageName, int clientUid, Bundle rootHints) {

    int maximumRootChildLimit = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
        /* defaultValue= */ 4);
    int supportedRootChildFlags = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
        /* defaultValue= */ MediaItem.FLAG_BROWSABLE);

    // rest of method...
}

Vous pouvez brancher la logique de votre hiérarchie de contenu en fonction des valeurs de ces suggestions, en particulier si votre hiérarchie varie entre les différentes intégrations de MediaBrowser en dehors d'Android Auto et d'Android Automotive OS. Par exemple, si vous affichez normalement un élément racine pouvant être lu, vous pouvez, à la place, l'imbriquer dans un élément racine consultable, en raison de la valeur de la suggestion des indicateurs acceptés.

En plus des suggestions de racine, vous devez suivre quelques consignes supplémentaires pour garantir un affichage optimal des onglets :

  1. Fournissez des icônes monochromes (de préférence blanches) pour chaque élément de l'onglet.
  2. Fournissez des libellés courts, mais explicites, pour chaque élément de l'onglet. En utilisant des libellés courts, il y a moins de risques que les chaînes soient tronquées.

Afficher une illustration de l'élément multimédia

Les illustrations d'éléments multimédias doivent être transmises sous la forme d'un URI local en utilisant ContentResolver.SCHEME_CONTENT ou ContentResolver.SCHEME_ANDROID_RESOURCE. Cet URI local doit correspondre à un bitmap ou un drawable vectoriel dans les ressources de l'application. Pour les objets MediaDescriptionCompat représentant des éléments de la hiérarchie de contenu, transmettez l'URI via setIconUri(). Pour les objets MediaMetadataCompat représentant l'élément en cours de lecture, transmettez l'URI via putString() à l'aide de l'une des clés suivantes :

L'exemple suivant montre comment télécharger une illustration à partir d'un URI Web et comment l'afficher via un URI local. Pour un exemple plus détaillé, consultez l'implémentation d'openFile() et des méthodes proches dans l'application exemple Universal Android Music Player.

  1. Créez un URI content:// correspondant à l'URI Web. Le service de navigateur multimédia et la session multimédia doivent transmettre cet URI de contenu à Android Auto et à Android Automotive OS.

    Kotlin

    fun Uri.asAlbumArtContentURI(): Uri {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(this.getPath()) // make sure you trust the URI!
        .build()
    }
    

    Java

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // make sure you trust the URI!
        .build();
    }
    
  2. Dans votre implémentation de ContentProvider.openFile(), vérifiez s'il existe un fichier pour l'URI correspondant. Si ce n'est pas le cas, téléchargez et mettez en cache le fichier image (l'extrait de code suivant utilise Glide).

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }
    

    Java

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }
    

Pour en savoir plus sur les fournisseurs de contenu, consultez Créer un fournisseur de contenu.

Appliquer des styles de contenu

Après avoir créé votre hiérarchie de contenu à l'aide d'éléments consultables ou lisibles, vous pouvez appliquer des styles de contenu pour déterminer la façon dont ils sont affichés sur l'écran de la voiture.

Vous pouvez utiliser les styles de contenu suivants :

Éléments de liste

Ce style de contenu donne la priorité aux titres et aux métadonnées par rapport aux images.

Éléments de grille

Ce style de contenu donne la priorité aux images par rapport aux titres et aux métadonnées.

Définir des styles de contenu par défaut

Vous pouvez définir des valeurs par défaut globales pour l'affichage de vos éléments multimédias en incluant certaines constantes dans le bundle d'extras BrowserRoot de la méthode onGetRoot() de votre service. Android Auto et Android Automotive OS lisent le bundle à la recherche de ces constantes afin de déterminer le style approprié.

Les extras suivants peuvent être utilisés comme clés dans le bundle :

Les clés peuvent correspondre aux valeurs constantes entières suivantes pour influencer la présentation de ces éléments :

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM : les éléments correspondants doivent être présentés sous la forme d'éléments de liste.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM : les éléments correspondants doivent être présentés sous la forme d'éléments de grille.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM : les éléments correspondants doivent être présentés sous la forme d'éléments de liste "catégorie". Ils sont semblables à des éléments de liste ordinaires, si ce n'est que des marges doivent être appliquées autour de leurs icônes, car une petite taille d'icône garantit un meilleur rendu. Les icônes doivent être des drawables vectoriels teintés. Cette suggestion ne doit être fournie que pour les éléments consultables.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM : les éléments correspondants doivent être présentés sous la forme d'éléments de grille "catégorie". Ils sont semblables à des éléments de grille ordinaires, si ce n'est que des marges doivent être appliquées autour de leurs icônes, car une petite taille d'icône garantit un meilleur rendu. Les icônes doivent être des drawables vectoriels teintés. Cette suggestion ne doit être fournie que pour les éléments consultables.

L'extrait de code suivant montre comment définir le style de contenu par défaut des éléments consultables sur des grilles et des éléments lisibles sur des listes :

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
override fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    return new BrowserRoot(ROOT_ID, extras);
}

Définir les styles de contenu par élément

L'API Content Style vous permet d'ignorer le style de contenu par défaut pour tous les enfants de l'élément multimédia consultable, ainsi que pour tout élément multimédia proprement dit.

Pour ignorer la valeur par défaut des enfants d'un élément multimédia consultable, créez un bundle d'extras dans la MediaDescription de l'élément multimédia et ajoutez les mêmes suggestions que celles décrites ci-dessus. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE s'applique aux enfants lisibles de cet élément, tandis que DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE concerne les enfants consultables.

Pour ignorer la valeur par défaut d'un élément multimédia proprement dit (et non ses enfants), créez un bundle d'extras dans la MediaDescription de l'élément multimédia et ajoutez une suggestion avec la clé DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM. Utilisez les mêmes valeurs que celles décrites ci-dessus pour spécifier la présentation de cet élément.

L'extrait de code suivant montre comment créer un MediaItem consultable qui ignore le style de contenu par défaut pour lui-même et pour ses enfants. Il se présente comme un élément de liste "catégorie", ses enfants consultables comme des éléments de liste et ses enfants lisibles comme des éléments de grille :

Kotlin

import androidx.media.utils.MediaConstants

private fun createBrowsableMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createBrowsableMediaItem(
    String mediaId,
    String folderName,
    Uri iconUri) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId(mediaId);
    mediaDescriptionBuilder.setTitle(folderName);
    mediaDescriptionBuilder.setIconUri(iconUri);
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    mediaDescriptionBuilder.setExtras(extras);
    return new MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}

Regrouper des éléments à l'aide de suggestions de titre

Pour regrouper des éléments multimédias associés, utilisez une suggestion par élément. Chaque élément multimédia d'un groupe doit déclarer un bundle d'extras dans sa MediaDescription, laquelle inclut une correspondance avec la clé DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE et une valeur de chaîne identique. Cette chaîne est utilisée comme titre du groupe et doit être localisée.

L'extrait de code suivant montre comment créer un MediaItem avec un en-tête de sous-groupe "Songs" :

Kotlin

import androidx.media.utils.MediaConstants

private fun createMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putString(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
        "Songs")
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

Votre application doit transmettre tous les éléments multimédias que vous souhaitez regrouper sous la forme d'un bloc contigu. Supposons, par exemple, que vous souhaitiez afficher deux groupes d'éléments multimédias "Songs" et "Albums" (dans cet ordre), et que votre application transmette cinq éléments multimédias dans l'ordre suivant :

  1. Élément multimédia A avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Élément multimédia B avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. Élément multimédia C avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Élément multimédia D avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. Élément multimédia E avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Étant donné que les éléments multimédias des groupes "Songs" et "Albums" ne sont pas regroupés dans des blocs contigus, Android Auto et Android Automotive OS interprètent cela comme s'il s'agissait de quatre groupes :

  • Groupe 1 intitulé "Songs" contenant l'élément multimédia A
  • Groupe 2 intitulé "Albums" contenant l'élément multimédia B
  • Groupe 3 intitulé "Songs" contenant les éléments multimédias C et D
  • Groupe 4 intitulé "Albums" contenant l'élément multimédia E

Pour afficher ces éléments dans deux groupes, votre application doit transmettre les applications dans l'ordre suivant :

  1. Élément multimédia A avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Élément multimédia C avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. Élément multimédia D avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Élément multimédia B avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  5. Élément multimédia E avec extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Afficher des indicateurs de métadonnées supplémentaires

Vous pouvez inclure des indicateurs de métadonnées supplémentaires pour fournir des informations immédiates sur le contenu dans l'arborescence du navigateur multimédia et pendant la lecture. Dans l'arborescence de navigation, Android Auto et Android Automotive OS lisent les extras associés à un élément et recherchent certaines constantes pour déterminer les indicateurs à afficher. Lors de la lecture de contenu multimédia, Android Auto et Android Automotive OS lisent les métadonnées de la session multimédia et recherchent certaines constantes pour déterminer les indicateurs à afficher.

Figure 2. Vue de lecture avec les métadonnées identifiant le titre et l'artiste, ainsi qu'une icône indiquant un contenu explicite

Figure 3. Vue de navigation affichant un point pour le contenu non lu au niveau du premier élément et une barre de progression pour le contenu lu en partie sur le deuxième élément

Les constantes suivantes peuvent être utilisées à la fois dans les extras de description MediaItem et dans les extras MediaMetadata :

Les constantes ci-dessous ne peuvent être utilisées que dans les extras de description MediaItem :

Pour afficher les indicateurs qui apparaissent lorsque l'utilisateur explore l'arborescence de navigation multimédia, créez un bundle d'extras contenant une ou plusieurs de ces constantes, puis transmettez-le à la méthode MediaDescription.Builder.setExtras().

L'extrait de code suivant montre comment afficher des indicateurs pour un élément multimédia explicite dont 70 % du contenu a été lu :

Kotlin

import androidx.media.utils.MediaConstants

val extras = Bundle()
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED)
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

Java

import androidx.media.utils.MediaConstants;

Bundle extras = new Bundle();
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build();
return new MediaBrowserCompat.MediaItem(description, /* flags */);

Pour afficher les indicateurs d'un élément multimédia en cours de lecture, vous pouvez déclarer des valeurs Long pour METADATA_KEY_IS_EXPLICIT ou EXTRA_DOWNLOAD_STATUS dans le MediaMetadataCompat de votre mediaSession. Vous ne pouvez pas afficher les indicateurs DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS ni DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE dans la vue de lecture.

L'extrait de code suivant montre comment indiquer que le titre qui se trouve actuellement dans la vue de lecture comporte du contenu explicite et a été téléchargé :

Kotlin

import androidx.media.utils.MediaConstants

mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build())

Java

import androidx.media.utils.MediaConstants;

mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build());

Mettre à jour la barre de progression dans la vue de navigation pendant la lecture du contenu

Comme mentionné précédemment, vous pouvez utiliser l'extra DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE pour afficher une barre de progression pour le contenu lu en partie dans la vue de navigation. Cependant, si un utilisateur poursuit la lecture de ce contenu à partir d'Android Auto ou d'Android Automotive OS, la précision de cet indicateur diminuera au fil du temps. Pour qu'Android Auto et Android Automotive OS assurent la mise à jour de la barre de progression, vous pouvez fournir des informations supplémentaires dans MediaMetadataCompat et PlaybackStateCompat afin d'associer le contenu en cours à des éléments multimédias de la vue de navigation. Pour que la mise à jour de la barre de progression de l'élément multimédia s'effectue automatiquement, les conditions suivantes doivent être remplies :

L'extrait de code suivant montre comment indiquer que l'élément en cours de lecture est associé à un élément de la vue de navigation :

Kotlin

import androidx.media.utils.MediaConstants

// When the MediaItem is constructed to show in the browse view
// Suppose the item was 25% complete when the user launched the browse view
val mediaItemExtras = Bundle()
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

// Elsewhere, when the user has selected MediaItem for playback
mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters
        .build())

val playbackStateExtras = Bundle()
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id")
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters
        .build())

Java

import androidx.media.utils.MediaConstants;

// When the MediaItem is constructed to show in the browse view
// Suppose the item was 25% complete when the user launched the browse view
Bundle mediaItemExtras = new Bundle();
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters
        .build();
return MediaBrowserCompat.MediaItem(description, /* flags */);

// Elsewhere, when the user has selected MediaItem for playback
mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters
        .build());

Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id");
mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters
        .build());

Figure 4. Vue de lecture avec l'option "Search results" (Résultats de recherche) pour l'affichage des éléments multimédias liés à la recherche vocale de l'utilisateur

Votre application peut fournir des résultats de recherche contextuels qui s'affichent lorsque les utilisateurs lancent une requête de recherche. Android Auto et Android Automotive OS affichent ces résultats via des interfaces de requête de recherche ou via des affordances axées sur les requêtes effectuées plus tôt dans la session. Pour en savoir plus, consultez la section Accepter les commandes vocales de cette page.

Pour afficher des résultats de recherche consultables, vous devez inclure la clé de constante BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED dans le bundle d'extras de la méthode onGetRoot() de votre service, en la mappant sur la valeur booléenne true.

L'extrait de code suivant montre comment activer la compatibilité dans la méthode onGetRoot() :

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true);
    return new BrowserRoot(ROOT_ID, extras);
}

Pour commencer à fournir des résultats de recherche, remplacez la méthode onSearch() dans votre service de navigateur multimédia. Android Auto et Android Automotive OS envoient les termes de recherche d'un utilisateur à cette méthode chaque fois qu'il appelle une interface de requête de recherche ou l'affordance "Résultats de recherche". Vous pouvez organiser les résultats de recherche de la méthode onSearch() de votre service à l'aide d'éléments de titre pour les rendre plus faciles à parcourir. Par exemple, si votre application lit de la musique, vous pouvez organiser les résultats de recherche par "Album", "Artist" (artiste) et "Songs" (chansons).

L'extrait de code suivant illustre une implémentation simple de la méthode onSearch() :

Kotlin

fun onSearch(query: String, extras: Bundle) {
  // Detach from results to unblock the caller (if a search is expensive)
  result.detach()
  object:AsyncTask() {
    internal var searchResponse:ArrayList
    internal var succeeded = false
    protected fun doInBackground(vararg params:Void):Void {
      searchResponse = ArrayList()
      if (doSearch(query, extras, searchResponse))
      {
        succeeded = true
      }
      return null
    }
    protected fun onPostExecute(param:Void) {
      if (succeeded)
      {
        // Sending an empty List informs the caller that there were no results.
        result.sendResult(searchResponse)
      }
      else
      {
        // This invokes onError() on the search callback
        result.sendResult(null)
      }
      return null
    }
  }.execute()
}
// Populates resultsToFill with search results. Returns true on success or false on error
private fun doSearch(
    query: String,
    extras: Bundle,
    resultsToFill: ArrayList
): Boolean {
  // Implement this method
}

Java

@Override
public void onSearch(final String query, final Bundle extras,
                        Result<List<MediaItem>> result) {

  // Detach from results to unblock the caller (if a search is expensive)
  result.detach();

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method
}

Activer la commande de lecture

Android Auto et Android Automotive OS envoient des commandes de lecture via la classe MediaSessionCompat de votre service. Vous devez enregistrer une session et implémenter les méthodes de rappel associées.

Enregistrer une session multimédia

Dans la méthode onCreate() de votre service de navigateur multimédia, créez une classe MediaSessionCompat, puis enregistrez la session multimédia en appelant setSessionToken().

L'extrait de code suivant montre comment créer et enregistrer une session multimédia :

Kotlin

override fun onCreate() {
    super.onCreate()

    ...
    // Start a new MediaSession
    val session = MediaSessionCompat(this, "session tag").apply {
        // Set a callback object to handle play control requests, which
        // implements MediaSession.Callback
        setCallback(MyMediaSessionCallback())
    }
    sessionToken = session.sessionToken

    ...
}

Java

public void onCreate() {
    super.onCreate();

    ...
    // Start a new MediaSession
    MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
    setSessionToken(session.getSessionToken());

    // Set a callback object to handle play control requests, which
    // implements MediaSession.Callback
    session.setCallback(new MyMediaSessionCallback());

    ...
}

Lorsque vous créez l'objet de session multimédia, vous définissez un objet de rappel utilisé pour gérer les requêtes de commande de lecture. Pour créer cet objet de rappel, vous devez fournir une implémentation de la classe MediaSessionCompat.Callback pour votre application. La section suivante vous explique comment implémenter cet objet.

Implémenter des commandes de lecture

Lorsqu'un utilisateur demande de lire un élément multimédia de votre application, Android Automotive OS et Android Auto utilisent la classe MediaSessionCompat.Callback de l'objet MediaSessionCompat qu'ils ont reçu du service de navigateur multimédia de l'application. Lorsqu'un utilisateur souhaite contrôler la lecture du contenu (par exemple, en mettant en pause la lecture ou en passant au titre suivant), Android Auto et Android Automotive OS appellent l'une des méthodes de l'objet de rappel.

Pour gérer la lecture de contenu, votre application doit étendre la classe abstraite MediaSessionCompat.Callback et implémenter les méthodes compatibles avec votre application.

Vous devez implémenter toutes les méthodes de rappel suivantes qui sont adaptées au type de contenu proposé par votre application :

onPrepare()
Appelée lorsque la source multimédia est modifiée. Android Automotive OS appelle également cette méthode juste après le démarrage. Votre application multimédia doit implémenter cette méthode.
onPlay()
Appelée si l'utilisateur lance la lecture sans choisir d'élément spécifique. Votre application doit lire le contenu par défaut. Si la lecture a été interrompue avec onPause(), l'application doit la reprendre.

Remarque : L'application ne doit pas lancer automatiquement la lecture de musique quand Android Automotive OS ou Android Auto se connecte à votre service de navigateur multimédia. Pour en savoir plus, consultez Définir l'état de lecture initial.

onPlayFromMediaId()
Appelée lorsque l'utilisateur lance la lecture d'un élément spécifique. Cette méthode transmet l'ID que votre service de navigateur multimédia a affecté à l'élément multimédia dans votre hiérarchie de contenu.
onPlayFromSearch()
Appelée lorsque l'utilisateur lance la lecture à partir d'une requête de recherche. L'application doit faire le bon choix en fonction de la chaîne de recherche qui a été transmise.
onPause()
Appelée lorsque l'utilisateur décide de suspendre la lecture.
onSkipToNext()
Appelée lorsque l'utilisateur décide de passer à l'élément suivant.
onSkipToPrevious()
Appelée lorsque l'utilisateur décide de revenir à l'élément précédent.
onStop()
Appelée lorsque l'utilisateur décide d'arrêter la lecture.

Votre application doit remplacer ces méthodes pour fournir la fonctionnalité souhaitée. Vous n'avez pas à implémenter une méthode si votre application ne l'accepte pas. Par exemple, si votre application diffuse du contenu en direct (comme un événement sportif), implémenter la méthode onSkipToNext() n'a aucun sens, et vous pouvez utiliser, à la place, l'implémentation par défaut d'onSkipToNext().

Votre application n'a pas besoin d'une logique particulière pour lire du contenu via les haut-parleurs de la voiture. Lorsque votre application reçoit une requête de lecture de contenu, elle doit lire le contenu comme elle le fait habituellement (par exemple, via le casque ou le haut-parleur du téléphone). Android Auto et Android Automotive OS envoient automatiquement le contenu audio au système de la voiture pour qu'il le diffuse sur ses haut-parleurs.

Pour en savoir plus sur la lecture de contenu audio, consultez Lecture de contenus multimédias, Gérer la lecture audio et ExoPlayer.

Définir des actions de lecture standards

Android Auto et Android Automotive OS affichent les commandes de lecture en fonction des actions activées dans l'objet PlaybackStateCompat.

Par défaut, votre application doit accepter les actions suivantes :

Votre application peut, en outre, accepter les actions suivantes si elles sont pertinentes par rapport à son contenu :

Vous pouvez également créer une file d'attente de lecture pouvant être présentée à l'utilisateur. Pour ce faire, vous devez appeler les méthodes setQueue() et setQueueTitle(), activer l'action ACTION_SKIP_TO_QUEUE_ITEM et définir le rappel onSkipToQueueItem(). Vous devez également utiliser l'icône En écoute, qui indique l'élément en cours de lecture. Pour ce faire, vous devez appeler la méthode setActiveQueueItemId() et transmettre l'ID de l'élément en cours de lecture dans la file d'attente. Vous devez mettre à jour setActiveQueueItemId() chaque fois qu'une file d'attente est modifiée.

Android Auto et Android Automotive OS affichent des boutons pour chaque action activée, ainsi que la file d'attente de lecture si vous choisissez d'en créer une. Lorsque l'utilisateur clique sur les boutons, le système appelle le rappel correspondant à partir de MediaSessionCompat.Callback.

Réserver de l'espace inutilisé

Android Auto et Android Automotive OS réservent de l'espace dans l'UI pour les actions ACTION_SKIP_TO_PREVIOUS et ACTION_SKIP_TO_NEXT. Si votre application n'est pas compatible avec l'une de ces fonctions, Android Auto et Android Automotive OS utilisent cet espace pour afficher les actions personnalisées que vous créez, le cas échéant.

Si vous ne souhaitez pas renseigner des actions personnalisées dans ces espaces, vous pouvez les réserver afin qu'Android Auto et Android Automotive OS les laissent vides chaque fois que votre application n'accepte pas la fonction correspondante. Pour ce faire, appelez la méthode setExtras() avec un bundle d'extras contenant des constantes correspondant aux fonctions réservées. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT correspond à ACTION_SKIP_TO_NEXT et SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV à ACTION_SKIP_TO_PREVIOUS. Utilisez ces constantes comme clés dans le bundle et utilisez la valeur booléenne true pour leurs valeurs.

Définir la classe PlaybackState initiale

Quand Android Auto et Android Automotive OS communiquent avec votre service de navigateur multimédia, votre session multimédia transmet l'état de lecture du contenu à l'aide de PlaybackStateCompat. L'application ne doit pas lancer automatiquement la lecture de musique quand Android Automotive OS ou Android Auto se connecte à votre service de navigateur multimédia. Au lieu de cela, Android Auto et Android Automotive OS doivent reprendre ou lancer la lecture en fonction de l'état de la voiture ou des actions de l'utilisateur.

Pour ce faire, définissez la classe PlaybackStateCompat initiale de votre session multimédia sur STATE_STOPPED, STATE_PAUSED, STATE_NONE ou STATE_ERROR.

La durée des sessions multimédias dans Android Auto et Android Automotive OS correspond à celle du trajet. Par conséquent, les utilisateurs lancent et arrêtent fréquemment ces sessions. Pour garantir une expérience optimale entre deux déplacements, effectuez le suivi de l'état de la session précédente (par exemple, le dernier élément multimédia lu, le PlaybackStateCompat et la file d'attente), de sorte que, lorsque l'application multimédia reçoit une demande de reprise, l'utilisateur puisse reprendre automatiquement là où il s'était arrêté.

Ajouter des actions de lecture personnalisées

Vous pouvez ajouter des actions de lecture personnalisées pour afficher plus d'actions compatibles avec votre application multimédia. Si l'espace le permet (et s'il n'est pas réservé), Android ajoute les actions personnalisées aux commandes de transport. Sinon, les actions personnalisées sont affichées dans le menu à développer. Ces actions s'affichent dans l'ordre dans lequel elles ont été ajoutées à PlaybackStateCompat.

Les actions personnalisées doivent avoir un comportement distinct des actions standards et ne doivent pas être utilisées pour remplacer ni dupliquer ces actions.

Vous pouvez ajouter des actions personnalisées à l'aide de la méthode addCustomAction() dans la classe PlaybackStateCompat.Builder.

L'extrait de code suivant montre comment ajouter une action "Lancer une chaîne de radio" personnalisée :

Kotlin

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

Java

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

Pour obtenir un exemple plus détaillé de cette méthode, consultez la méthode setCustomAction() dans l'application exemple Universal Android Music Player sur GitHub.

Une fois que vous avez créé votre action personnalisée, votre session multimédia peut y répondre en remplaçant la méthode onCustomAction().

L'extrait de code suivant montre comment votre application peut répondre à une action "Lancer une chaîne de radio" :

Kotlin

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

Java

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

Pour obtenir un exemple plus détaillé de cette méthode, consultez la méthode onCustomAction dans l'application exemple Universal Android Music Player sur GitHub.

Icônes des actions personnalisées

Chaque action personnalisée que vous créez a besoin d'une ressource d'icône. Les applications utilisées dans les voitures peuvent être exécutées sur un large éventail de tailles et de densités d'écran. Les icônes que vous fournissez doivent donc être des drawables vectoriels. Un drawable vectoriel permet de dimensionner des éléments en conservant le même niveau de détail. Ce type de graphique facilite également l'alignement des contours et des coins sur les limites de pixels à des résolutions inférieures.

Dans le cas d'une action personnalisée avec état (par exemple, elle active ou désactive un paramètre de lecture), fournissez différentes icônes pour les différents états, de sorte que les utilisateurs perçoivent un changement visuel lorsqu'ils sélectionnent l'action.

Fournir un autre style d'icône pour les actions désactivées

Si une action personnalisée n'est pas disponible pour le contexte actuel, remplacez l'icône d'action personnalisée par une autre indiquant que l'action est désactivée.

Figure 5. Exemples d'icônes d'action personnalisées hors style

Accepter les commandes vocales

Votre application multimédia doit accepter les commandes vocales pour offrir aux conducteurs un environnement sûr et pratique qui minimise les distractions. Par exemple, si votre application lit déjà un élément multimédia, l'utilisateur peut dire "Lire [titre de la chanson]" (par exemple, "Lire Bohemian Rhapsody") pour demander à l'application de diffuser une autre chanson sans regarder ni toucher l'écran de la voiture. Les utilisateurs peuvent lancer des requêtes en cliquant sur les boutons appropriés sur le volant ou en énonçant le mot clé Ok Google à voix haute.

Quand Android Auto ou Android Automotive OS détecte et interprète une commande vocale, elle est envoyée à l'application via onPlayFromSearch(). À la réception de ce rappel, l'application doit rechercher le contenu correspondant à la chaîne query et lancer la lecture.

Les utilisateurs peuvent spécifier différentes catégories de termes dans leur requête : genre, artiste, album, nom de la chanson, station de radio, playlist, etc. Lorsque vous développez la compatibilité de la recherche, tenez compte de toutes les catégories pertinentes pour votre application. Si Android Auto ou Android Automotive OS détecte qu'une requête donnée correspond à certaines catégories, des extras sont ajoutés dans le paramètre extras. Les extras suivants peuvent être envoyés :

L'application multimédia doit prendre en compte une chaîne query vide. Elle peut être envoyée par Android Auto ou Android Automotive OS si l'utilisateur ne spécifie pas de termes de recherche (par exemple, s'il dit "Lire de la musique"). Dans ce cas, l'application peut décider de lancer un titre écouté récemment ou une nouvelle suggestion.

Si une recherche ne peut pas être traitée rapidement, ne bloquez pas dans onPlayFromSearch(). Définissez plutôt l'état de lecture sur STATE_CONNECTING et effectuez la recherche sur un thread asynchrone.

Une fois que la lecture a commencé, pensez à ajouter le contenu associé dans la file d'attente de la session. Par exemple, si l'utilisateur a demandé la lecture d'un album, l'application peut renseigner la liste des titres de cet album dans la file d'attente. Pensez également à accepter les résultats de recherche consultables, de sorte qu'un utilisateur puisse choisir un autre titre correspondant à sa requête.

Outre les requêtes lire, Android Auto et Android Automotive OS reconnaissent les requêtes vocales permettant de contrôler la lecture, comme mettre la musique en pause et titre suivant. Ils associent également ces commandes aux rappels de session multimédia appropriés, comme onPause() et onSkipToNext().

Pour obtenir un exemple détaillé de la méthode d'implémentation des actions de lecture par commande vocale dans votre application, consultez L'Assistant Google et les applications multimédias.

Implémenter des mesures de protection contre la distraction

Lorsque le conducteur utilise Android Auto, son smartphone est connecté aux haut-parleurs de la voiture. Vous devez donc prendre des précautions supplémentaires pour éviter toute distraction.

Supprimer les alarmes dans le véhicule

Une application multimédia Android Auto ne peut lire du contenu audio via les haut-parleurs de la voiture que si l'utilisateur lance délibérément la lecture (par exemple, en appuyant sur le bouton de lecture de l'application). Cela vaut également pour une alarme programmée par l'utilisateur depuis l'application multimédia. Pour satisfaire à cette exigence, votre application peut utiliser CarConnection comme signal avant de lire du contenu audio. Pour déterminer si le téléphone projette actuellement du contenu sur l'écran d'une voiture, l'application peut vérifier si la classe LiveData du type de connexion de la voiture est égale à CONNECTION_TYPE_PROJECTION.

Si une projection de contenu est en cours, l'application multimédia compatible avec les alarmes doit effectuer l'une des opérations suivantes :

  • Désactiver l'alarme.
  • Lire l'alarme via STREAM_ALARM et afficher une UI sur l'écran du téléphone pour la désactiver.

Gérer les publicités multimédias

Par défaut, Android Auto affiche une notification lorsque les métadonnées multimédias changent au cours d'une session de lecture audio. Lorsqu'une application multimédia passe de la lecture de musique à la diffusion d'une publicité, l'affichage d'une notification risquerait de distraire l'utilisateur (et s'avérerait donc inutile). Pour empêcher Android Auto d'afficher une notification, vous devez définir la clé des métadonnées multimédias METADATA_KEY_IS_ADVERTISEMENT sur METADATA_VALUE_ATTRIBUTE_PRESENT., comme dans l'extrait de code ci-dessous :

Kotlin

import androidx.media.utils.MediaConstants

override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
    MediaMetadataCompat.Builder().apply {
        if (isAd(mediaId)) {
            putLong(
                MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        }
        // ...add any other properties as you normally would
        mediaSession.setMetadata(build())
    }
}

Java

import androidx.media.utils.MediaConstants;

@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
    if (isAd(mediaId)) {
        builder.putLong(
            MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
    }
    // ...add any other properties as you normally would
    mediaSession.setMetadata(builder.build());
}

Gérer les erreurs générales

Lorsque l'application rencontre une erreur, vous devez définir l'état de lecture sur STATE_ERROR et fournir un message d'erreur à l'aide de la méthode setErrorMessage(). Les messages d'erreur doivent être visibles par l'utilisateur et localisés en fonction de ses paramètres régionaux actuels. Android Auto et Android Automotive OS peuvent alors afficher le message d'erreur.

Pour en savoir plus sur les états d'erreur, consultez Utiliser une session multimédia : états et erreurs.

Si un utilisateur d'Android Auto doit ouvrir l'application pour téléphone pour résoudre une erreur, votre message doit l'en informer. Par exemple, votre message d'erreur doit indiquer "Connectez-vous à [nom de votre application]" plutôt que "Veuillez vous connecter".

Autres ressources