Medien-Apps für Autos entwickeln

Mit Android Auto und Android Automotive OS können Sie die Inhalte Ihrer Medien-App Nutzern im Auto zur Verfügung stellen. Eine Medien-App für Autos muss einen Medienbrowserdienst bereitstellen, damit Android Auto und Android Automotive OS oder eine andere App mit einem Medienbrowser Ihre Inhalte finden und anzeigen können.

In diesem Leitfaden wird davon ausgegangen, dass Sie bereits eine Medien-App haben, die Audio auf einem Smartphone abspielt, und dass Ihre Medien-App der Architektur von Android-Medien-Apps entspricht.

In diesem Leitfaden werden die erforderlichen Komponenten von MediaBrowserService und MediaSession beschrieben, die Ihre App benötigt, um unter Android Auto oder Android Automotive OS zu funktionieren. Nachdem Sie die grundlegende Medieninfrastruktur fertiggestellt haben, können Sie Ihrer Medien-App Unterstützung für Android Auto und Unterstützung für Android Automotive OS hinzufügen.

Hinweis

  1. Weitere Informationen finden Sie in der Dokumentation zur Android Media API.
  2. Weitere Informationen zum Design finden Sie unter Medien-Apps erstellen.
  3. Sehen Sie sich die in diesem Abschnitt aufgeführten wichtigen Begriffe und Konzepte an.

Wichtige Begriffe und Konzepte

Medienbrowserdienst
Ein von deiner Medien-App implementierter Android-Dienst, der der MediaBrowserServiceCompat API entspricht. Ihre App verwendet diesen Dienst, um ihre Inhalte zu präsentieren.
Medienbrowser
Eine API, die von Medien-Apps verwendet wird, um Medienbrowserdienste zu finden und deren Inhalte anzuzeigen. Android Auto und Android Automotive OS verwenden einen Medienbrowser, um den Medienbrowserdienst Ihrer App zu finden.
Medienelement

Der Medienbrowser organisiert seine Inhalte in einem Baum aus MediaItem-Objekten. Ein Medienelement kann eines oder beide der folgenden Flags haben:

  • FLAG_PLAYABLE: Gibt an, dass das Element ein Endknoten im Inhaltsbaum ist. Das Element stellt einen einzelnen Audiostream dar, z. B. einen Titel auf einem Album, ein Kapitel in einem Hörbuch oder eine Folge eines Podcasts.
  • FLAG_BROWSABLE: gibt an, dass das Element ein Knoten im Inhaltsbaum ist und untergeordnete Elemente hat. Angenommen, das Element steht für ein Album und seine untergeordneten Elemente sind die Titel auf dem Album.

Ein Medienelement, das sowohl durchsucht als auch abgespielt werden kann, funktioniert wie eine Playlist. Sie können das Element selbst auswählen, um alle untergeordneten Elemente abzuspielen, oder die untergeordneten Elemente durchsuchen.

Fahrzeugoptimiert

Eine Aktivität für eine Android Automotive OS-App, die den Designrichtlinien für Android Automotive OS entspricht. Die Benutzeroberfläche für diese Aktivitäten wird nicht von Android Automotive OS erstellt. Sie müssen also dafür sorgen, dass Ihre App den Designrichtlinien entspricht. Dazu gehören in der Regel größere Tippziele und Schriftgrößen, Unterstützung für Tag- und Nachtmodus sowie höhere Kontrastverhältnisse.

Für Fahrzeuge optimierte Benutzeroberflächen dürfen nur angezeigt werden, wenn keine Einschränkungen für die Nutzerfreundlichkeit von Autos gelten, da diese Oberflächen eine längere Aufmerksamkeit oder Interaktion des Nutzers erfordern können. CUXRs sind nicht aktiv, wenn das Auto steht oder parkt, aber immer, wenn es in Bewegung ist.

Sie müssen keine Aktivitäten für Android Auto entwerfen, da Android Auto eine eigene für das Fahrzeug optimierte Benutzeroberfläche mithilfe der Informationen aus Ihrem Media-Browserdienst erstellt.

Manifestdateien Ihrer App konfigurieren

Bevor Sie Ihren Media-Browser-Dienst erstellen können, müssen Sie die Manifestdateien Ihrer App konfigurieren.

Medienbrowserdienst deklarieren

Sowohl Android Auto als auch Android Automotive OS stellen über Ihren Medienbrowserdienst eine Verbindung zu Ihrer App her, um Medienelemente zu durchsuchen. Deklarieren Sie Ihren Medienbrowserdienst in Ihrem Manifest, damit Android Auto und Android Automotive OS den Dienst finden und eine Verbindung zu Ihrer App herstellen können.

Das folgende Code-Snippet zeigt, wie du deinen Media-Browser-Dienst in deinem Manifest deklarierst. Fügen Sie diesen Code in die Manifestdatei für Ihr Android Automotive OS-Modul und in die Manifestdatei für Ihre Smartphone-App ein.

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

App-Symbole angeben

Sie müssen App-Symbole angeben, die Android Auto und Android Automotive OS verwenden können, um Ihre App in der System-UI darzustellen. Es sind zwei Symboltypen erforderlich:

  • Launcher-Symbol
  • Attributionssymbol

Launcher-Symbol

Das Launcher-Symbol steht für Ihre App in der System-UI, z. B. im Launcher und in der Symbolleiste. Mit der folgenden Manifestdeklaration können Sie angeben, dass das Symbol Ihrer mobilen App für Ihre Automedien-App verwendet werden soll:

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

Wenn Sie ein anderes Symbol als das Ihrer mobilen App verwenden möchten, legen Sie die android:icon-Eigenschaft für das <service>-Element Ihres Media-Browser-Dienstes im Manifest fest:

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

Attributionssymbol

Abbildung 1. Attributionssymbol auf der Medienkarte

Das Attributionssymbol wird an Stellen verwendet, an denen Medieninhalte Vorrang haben, z. B. auf Medienkarten. Sie können das kleine Symbol für Benachrichtigungen wiederverwenden. Dieses Symbol muss einfarbig sein. Sie können ein Symbol angeben, das für Ihre App verwendet wird, mit der folgenden Manifestdeklaration:

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

Medienbrowserdienst erstellen

Sie erstellen einen Medienbrowserdienst, indem Sie die Klasse MediaBrowserServiceCompat erweitern. Sowohl Android Auto als auch Android Automotive OS können Ihren Dienst dann für Folgendes verwenden:

  • Durchsuchen Sie die Inhaltshierarchie Ihrer App, um den Nutzern ein Menü anzuzeigen.
  • Rufe das Token für das MediaSessionCompat-Objekt deiner App ab, um die Audiowiedergabe zu steuern.

Sie können Ihren Media-Browser-Dienst auch verwenden, um anderen Clients den Zugriff auf Medieninhalte aus Ihrer App zu ermöglichen. Diese Media-Clients können andere Apps auf dem Smartphone eines Nutzers oder andere Remote-Clients sein.

Workflow für Medienbrowserdienste

In diesem Abschnitt wird beschrieben, wie Android Automotive OS und Android Auto während eines typischen Nutzerworkflows mit Ihrem Media-Browserdienst interagieren.

  1. Der Nutzer startet Ihre App unter Android Automotive OS oder Android Auto.
  2. Android Automotive OS oder Android Auto kontaktiert den Media-Browser-Dienst Ihrer App über die Methode onCreate(). Bei der Implementierung der onCreate()-Methode müssen Sie ein MediaSessionCompat-Objekt und sein Callback-Objekt erstellen und registrieren.
  3. Android Automotive OS oder Android Auto ruft die Methode onGetRoot() deines Dienstes auf, um das Stammmedienelement in deiner Inhaltshierarchie abzurufen. Das Stammmedienelement wird nicht angezeigt, sondern dient dazu, weitere Inhalte aus Ihrer App abzurufen.
  4. Android Automotive OS oder Android Auto ruft die Methode onLoadChildren() deines Dienstes auf, um die untergeordneten Elemente des Stammmediumelements abzurufen. In Android Automotive OS und Android Auto werden diese Medienelemente als oberste Ebene der Inhaltselemente angezeigt. Weitere Informationen dazu, was das System auf dieser Ebene erwartet, finden Sie auf dieser Seite unter Übergeordnetes Menü strukturieren.
  5. Wenn der Nutzer ein suchbares Medienelement auswählt, wird die onLoadChildren()-Methode deines Dienstes noch einmal aufgerufen, um die untergeordneten Elemente des ausgewählten Menüpunkts abzurufen.
  6. Wenn der Nutzer ein abspielbares Medienelement auswählt, ruft Android Automotive OS oder Android Auto die entsprechende Rückrufmethode der Mediensitzung auf, um diese Aktion auszuführen.
  7. Wenn von Ihrer App unterstützt, kann der Nutzer auch in Ihren Inhalten suchen. In diesem Fall rufen Android Automotive OS oder Android Auto die Methode onSearch() Ihres Dienstes auf.

Inhaltshierarchie erstellen

Android Auto und Android Automotive OS rufen den Medienbrowserdienst Ihrer App auf, um herauszufinden, welche Inhalte verfügbar sind. Dazu müssen Sie in Ihrem Media-Browser-Dienst zwei Methoden implementieren: onGetRoot() und onLoadChildren().

onGetRoot implementieren

Die onGetRoot()-Methode deines Dienstes gibt Informationen zum Stammknoten deiner Inhaltshierarchie zurück. Android Auto und Android Automotive OS verwenden diesen Stammknoten, um den Rest Ihrer Inhalte mit der Methode onLoadChildren() anzufordern.

Das folgende Code-Snippet zeigt eine einfache Implementierung der Methode 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);
}

Ein ausführlicheres Beispiel für diese Methode finden Sie in der Methode onGetRoot() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Paketüberprüfung für onGetRoot() hinzufügen

Wenn die onGetRoot()-Methode Ihres Dienstes aufgerufen wird, übergibt das aufrufende Paket identifizierende Informationen an Ihren Dienst. Anhand dieser Informationen kann dein Dienst entscheiden, ob dieses Paket auf deine Inhalte zugreifen kann. Sie können beispielsweise den Zugriff auf die Inhalte Ihrer App auf eine Liste genehmigter Pakete beschränken, indem Sie die clientPackageName mit Ihrer Zulassungsliste vergleichen und das Zertifikat prüfen, mit dem das APK des Pakets signiert wurde. Wenn das Paket nicht bestätigt werden kann, gib null zurück, um den Zugriff auf deine Inhalte zu verweigern.

Damit System-Apps wie Android Auto und Android Automotive OS auf Ihre Inhalte zugreifen können, muss Ihr Dienst immer eine nicht nullwertige BrowserRoot zurückgeben, wenn diese System-Apps die Methode onGetRoot() aufrufen. Die Signatur der Android Automotive OS-System-App kann je nach Marke und Modell des Autos variieren. Sie müssen daher Verbindungen von allen System-Apps zulassen, um Android Automotive OS zuverlässig zu unterstützen.

Das folgende Code-Snippet zeigt, wie Ihr Dienst prüfen kann, ob es sich beim aufrufenden Paket um eine System-App handelt:

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
}

Dieses Code-Snippet ist ein Auszug aus der Klasse PackageValidator in der Beispielanwendung „Universal Android Music Player“ auf GitHub. In dieser Klasse finden Sie ein detaillierteres Beispiel für die Implementierung der Paketüberprüfung für die onGetRoot()-Methode Ihres Dienstes.

Sie müssen nicht nur System-Apps zulassen, sondern auch Google Assistant erlauben, eine Verbindung zu Ihrem MediaBrowserService herzustellen. Google Assistant hat separate Paketnamen für das Smartphone, einschließlich Android Auto, und für Android Automotive OS.

onLoadChildren() implementieren

Nachdem Android Auto und Android Automotive OS das Stammknotenobjekt erhalten haben, erstellen sie ein Menü der obersten Ebene, indem sie onLoadChildren() auf das Stammknotenobjekt aufrufen, um seine untergeordneten Elemente abzurufen. Client-Apps erstellen Untermenüs, indem sie dieselbe Methode mit untergeordneten Knotenobjekten aufrufen.

Jeder Knoten in Ihrer Inhaltshierarchie wird durch ein MediaBrowserCompat.MediaItem-Objekt dargestellt. Jedes dieser Medienelemente wird durch einen eindeutigen ID-String identifiziert. Client-Apps behandeln diese ID-Strings als intransparente Tokens. Wenn eine Client-App ein Untermenü aufrufen oder ein Medienelement wiedergeben möchte, gibt sie das Token weiter. Deine App ist dafür verantwortlich, das Token mit dem entsprechenden Medienelement zu verknüpfen.

Das folgende Code-Snippet zeigt eine einfache Implementierung der Methode 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 whether 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 whether 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);
}

Ein vollständiges Beispiel für diese Methode finden Sie in der Methode onLoadChildren() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Stammmenü strukturieren

Abbildung 2. Stamminhalte werden als Navigationstabs angezeigt.

Android Auto und Android Automotive OS haben spezifische Einschränkungen hinsichtlich der Struktur des Stammmenüs. Diese werden über Root-Hinweise an die MediaBrowserService gesendet, die über das Argument Bundle gelesen werden können, das an onGetRoot() übergeben wird. Wenn Sie diesen Hinweisen folgen, kann das System die Stamminhalte optimal als Navigationsleisten anzeigen. Wenn Sie diese Hinweise nicht beachten, werden einige Stamminhalte möglicherweise vom System entfernt oder weniger sichtbar gemacht. Es werden zwei Hinweise gesendet:

Verwenden Sie den folgenden Code, um die relevanten Root-Hinweise zu lesen:

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...
}

Du kannst die Logik für die Struktur deiner Inhaltshierarchie basierend auf den Werten dieser Hinweise verzweigen, insbesondere wenn deine Hierarchie zwischen MediaBrowser-Integrationen außerhalb von Android Auto und Android Automotive OS variiert. Wenn du beispielsweise normalerweise ein übergeordnetes abspielbares Element darstellst, solltest du es aufgrund des Werts des Hinweises zu unterstützten Flags stattdessen in einem übergeordneten durchsuchbaren Element verschachteln.

Neben den Stammhinweiselementen gibt es noch einige weitere Richtlinien, die Sie beachten sollten, damit Tabs optimal gerendert werden:

  • Verwenden Sie für jedes Tabelement einfarbige, vorzugsweise weiße Symbole.
  • Geben Sie für jedes Tabelement kurze, aber aussagekräftige Labels an. Wenn Sie Labels kurz halten, verringert sich die Wahrscheinlichkeit, dass die Strings abgeschnitten werden.

Media-Artwork anzeigen

Artwork für Medienelemente muss als lokaler URI mit ContentResolver.SCHEME_CONTENT oder ContentResolver.SCHEME_ANDROID_RESOURCE übergeben werden. Dieser lokale URI muss in den Ressourcen der Anwendung entweder auf eine Bitmap oder ein Vektor-Zeichnen verweisen. Für MediaDescriptionCompat-Objekte, die Elemente in der Inhaltshierarchie darstellen, übergeben Sie den URI über setIconUri(). Für MediaMetadataCompat-Objekte, die das aktuell wiedergegebene Element darstellen, leite die URI über putString() weiter. Verwende dazu einen der folgenden Schlüssel:

In den folgenden Schritten wird beschrieben, wie du Artwork über einen Web-URI herunterlädst und über einen lokalen URI verfügbar machst. Ein vollständigeres Beispiel findest du in der Implementierung von openFile() und den zugehörigen Methoden in der Beispiel-App „Universal Android Music Player“.

  1. Erstelle einen content://-URI, der dem Web-URI entspricht. Der Medienbrowserdienst und die Mediensitzung geben diesen Inhalts-URI an Android Auto und Android Automotive OS weiter.

    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. Prüfe in deiner Implementierung von ContentProvider.openFile(), ob eine Datei für den entsprechenden URI vorhanden ist. Andernfalls laden Sie die Bilddatei herunter und speichern Sie sie im Cache. Im folgenden Code-Snippet wird Glide verwendet.

    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);
    }
    

Weitere Informationen zu Inhaltsanbietern finden Sie unter Inhaltsanbieter erstellen.

Inhaltsstile anwenden

Nachdem Sie Ihre Inhaltshierarchie mit durchsuchbaren oder abspielbaren Elementen erstellt haben, können Sie Inhaltsstile anwenden, die festlegen, wie diese Elemente im Auto angezeigt werden.

Sie können die folgenden Inhaltsstile verwenden:

Listenelemente

Bei diesem Inhaltsstil haben Titel und Metadaten Vorrang vor Bildern.

Rasterelemente

Bei diesem Inhaltsstil haben Bilder Vorrang vor Titeln und Metadaten.

Standardinhaltsstile festlegen

Du kannst globale Standardeinstellungen für die Darstellung deiner Medienelemente festlegen, indem du bestimmte Konstanten in das BrowserRoot-Extras-Bundle der onGetRoot()-Methode deines Dienstes aufnimmst. Android Auto und Android Automotive OS lesen dieses Bundle und suchen nach diesen Konstanten, um den entsprechenden Stil zu ermitteln.

Die folgenden Extras können als Schlüssel im Bundle verwendet werden:

Die Schlüssel können den folgenden Ganzzahlkonstantenwerten zugeordnet werden, um die Darstellung dieser Elemente zu beeinflussen:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: Die entsprechenden Elemente werden als Listenelemente dargestellt.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: Die entsprechenden Elemente werden als Rasterelemente dargestellt.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: Die entsprechenden Elemente werden als Listenelemente der Kategorie angezeigt. Diese Elemente ähneln normalen Listenelementen, mit dem Unterschied, dass um die Symbole der Elemente Ränder angewendet werden, da die Symbole bei kleiner Größe besser aussehen. Die Symbole müssen färbbare Vektor-Drawables sein. Dieser Hinweis wird voraussichtlich nur für Elemente angezeigt, die sich durchsuchen lassen.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: Die entsprechenden Elemente werden als Rasterelemente der Kategorie angezeigt. Diese Elemente ähneln normalen Rasterelementen, mit dem Unterschied, dass um die Symbole der Elemente Ränder angewendet werden, da sie bei kleiner Größe besser aussehen. Die Symbole müssen färbbare Vektor-Drawables sein. Dieser Hinweis wird voraussichtlich nur für Elemente angezeigt, die sich durchsuchen lassen.

Im folgenden Code-Snippet wird gezeigt, wie Sie den Standardinhaltsstil für suchbare Elemente auf „Raster“ und für abspielbare Elemente auf „Listen“ festlegen:

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);
}

Inhaltsstile pro Artikel festlegen

Mit der Content Style API kannst du den Standardinhaltsstil für alle untergeordneten Elemente eines suchbaren Medienelements sowie für das Medienelement selbst überschreiben.

Wenn du die Standardeinstellung für die untergeordneten Elemente eines suchbaren Medienelements überschreiben möchtest, erstelle ein Extras-Bundle in der MediaDescription des Medienelements und füge dieselben zuvor genannten Hinweise hinzu. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE gilt für die abspielbaren untergeordneten Elemente dieses Elements, während DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE für die suchbaren untergeordneten Elemente dieses Elements gilt.

Wenn du den Standardwert für ein bestimmtes Medienelement selbst und nicht für seine untergeordneten Elemente überschreiben möchtest, erstelle ein Extras-Bundle in der MediaDescription des Medienelements und füge einen Hinweis mit dem Schlüssel DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM hinzu. Verwenden Sie die oben beschriebenen Werte, um die Darstellung dieses Artikels anzugeben.

Das folgende Code-Snippet zeigt, wie eine durchsuchbare MediaItem erstellt wird, die den Standardinhaltsstil sowohl für sich selbst als auch für ihre untergeordneten Elemente überschreibt. Es wird als Kategorielistenelement, seine durchsuchbaren untergeordneten Elemente als Listenelemente und seine abspielbaren untergeordneten Elemente als Rasterelemente dargestellt:

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);
}

Elemente mithilfe von Titelhinweisen gruppieren

Wenn du ähnliche Medienelemente gruppieren möchtest, verwendest du einen Hinweis pro Element. Für jedes Medienelement in einer Gruppe muss in seinem MediaDescription ein Extras-Bundle deklariert werden, das eine Zuordnung mit dem Schlüssel DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE und einem identischen Stringwert enthält. Lokalisieren Sie diesen String, der als Titel der Gruppe verwendet wird.

Im folgenden Code-Snippet wird gezeigt, wie ein MediaItem mit der Überschrift "Songs" für eine Untergruppe erstellt wird:

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*/);
}

Deine App muss alle Medienelemente übergeben, die du als zusammenhängenden Block gruppieren möchtest. Angenommen, Sie möchten zwei Gruppen von Medienelementen, „Songs“ und „Alben“, in dieser Reihenfolge anzeigen lassen und Ihre App gibt fünf Medienelemente in der folgenden Reihenfolge an:

  1. Medienelement A mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Medienelement B mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. Medienelement C mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Medienelement D mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. Medienelement E mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Da die Medienelemente der Gruppe „Songs“ und der Gruppe „Alben“ nicht in zusammenhängenden Blöcken zusammengefasst werden, werden sie in Android Auto und Android Automotive OS als die folgenden vier Gruppen interpretiert:

  • Gruppe 1 mit dem Namen „Songs“ mit dem Medienelement A
  • Gruppe 2 mit dem Namen „Alben“ mit dem Medienelement B
  • Gruppe 3 mit dem Namen „Songs“ mit den Medienelementen C und D
  • Gruppe 4 mit dem Namen „Alben“ mit dem Medienelement E

Wenn diese Elemente in zwei Gruppen angezeigt werden sollen, muss Ihre App die Medienelemente stattdessen in der folgenden Reihenfolge übergeben:

  1. Medienelement A mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Medienelement C mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. Medienelement D mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Medienelement B mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  5. Medienelement E mit extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Zusätzliche Metadaten-Indikatoren anzeigen

Du kannst zusätzliche Metadaten-Indikatoren einfügen, um Informationen zu Inhalten im Media-Browser-Baum und während der Wiedergabe auf einen Blick verfügbar zu machen. Im Navigationsbaum lesen Android Auto und Android Automotive OS die mit einem Element verknüpften Extras und suchen nach bestimmten Konstanten, um zu bestimmen, welche Indikatoren angezeigt werden sollen. Während der Medienwiedergabe lesen Android Auto und Android Automotive OS die Metadaten für die Mediensitzung und suchen nach bestimmten Konstanten, um die anzuzeigenden Indikatoren zu bestimmen.

Abbildung 3 Wiedergabeansicht mit Metadaten, die den Titel und den Künstler identifizieren, sowie ein Symbol für anstößige Inhalte.

Abbildung 4 In der Ansicht „Suchen“ ist beim ersten Element ein Punkt für nicht wiedergegebene Inhalte und beim zweiten Element eine Fortschrittsanzeige für teilweise wiedergegebene Inhalte zu sehen.

Die folgenden Konstanten können sowohl in MediaItem-Beschreibungselementen als auch in MediaMetadata-Elementen verwendet werden:

Die folgenden Konstanten können nur in MediaItem-Textzeilen-Extras verwendet werden:

Wenn du Indikatoren anzeigen möchtest, die angezeigt werden, während der Nutzer im Medien-Browse-Tree stöbert, erstelle ein Extras-Bundle mit einer oder mehreren dieser Konstanten und übergebe dieses Bundle an die MediaDescription.Builder.setExtras()-Methode.

Im folgenden Code-Snippet wird gezeigt, wie Indikatoren für ein explizites Medienelement angezeigt werden, das zu 70% fertig ist:

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 */);

Wenn du Indikatoren für ein gerade wiedergegebenes Medienelement anzeigen möchtest, kannst du Long-Werte für METADATA_KEY_IS_EXPLICIT oder EXTRA_DOWNLOAD_STATUS im MediaMetadataCompat deiner mediaSession angeben. Die Indikatoren DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS oder DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE können nicht in der Wiedergabeansicht angezeigt werden.

Im folgenden Code-Snippet wird gezeigt, wie du angeben kannst, dass der aktuelle Titel in der Wiedergabeansicht explizit und heruntergeladen ist:

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());

Fortschrittsanzeige in der Wiedergabeansicht während der Wiedergabe aktualisieren

Wie bereits erwähnt, kannst du mit dem Extra DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE eine Fortschrittsanzeige für teilweise abgespielte Inhalte in der Suche anzeigen lassen. Wenn ein Nutzer die teilweise abgespielten Inhalte jedoch weiterhin über Android Auto oder Android Automotive OS abspielt, wird dieser Indikator mit der Zeit ungenau.

Damit die Fortschrittsanzeige in Android Auto und Android Automotive OS immer auf dem neuesten Stand ist, kannst du unter MediaMetadataCompat und PlaybackStateCompat zusätzliche Informationen angeben, um aktuelle Inhalte mit Medienelementen in der Ansicht „Suchen“ zu verknüpfen. Damit das Medienelement eine automatisch aktualisierte Fortschrittsanzeige hat, müssen die folgenden Anforderungen erfüllt sein:

Im folgenden Code-Snippet wird gezeigt, wie angegeben wird, dass das gerade wiedergegebene Element mit einem Element in der Ansicht „Suchen“ verknüpft ist:

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());

Abbildung 5. Wiedergabeansicht mit der Option „Suchergebnisse“, um sich Medienelemente anzusehen, die sich auf die Sprachsuche des Nutzers beziehen.

Ihre App kann kontextbezogene Suchergebnisse bereitstellen, die Nutzern angezeigt werden, wenn sie eine Suchanfrage starten. In Android Auto und Android Automotive OS werden diese Ergebnisse über Suchanfrageoberflächen oder über Funktionen angezeigt, die sich auf Suchanfragen beziehen, die zuvor in der Sitzung gestellt wurden. Weitere Informationen finden Sie in diesem Leitfaden im Abschnitt Sprachaktionen unterstützen.

Wenn Sie durchsuchbare Suchergebnisse anzeigen möchten, fügen Sie den Konstantenschlüssel BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED in das Extras-Bundle der onGetRoot()-Methode Ihres Dienstes ein und ordnen Sie ihn dem booleschen Wert true zu.

Das folgende Code-Snippet zeigt, wie Sie die Unterstützung in der Methode onGetRoot() aktivieren:

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);
}

Wenn du Suchergebnisse bereitstellen möchtest, überschreibe die Methode onSearch() in deinem Media-Browser-Dienst. Android Auto und Android Automotive OS leiten die Suchbegriffe des Nutzers an diese Methode weiter, wenn ein Nutzer eine Suchanfrageoberfläche oder die Option „Suchergebnisse“ aufruft.

Sie können die Suchergebnisse der onSearch()-Methode Ihres Dienstes mithilfe von Titelelementen organisieren, um sie übersichtlich zu gestalten. Wenn Ihre App beispielsweise Musik abspielt, können Sie die Suchergebnisse nach Album, Künstler und Titeln sortieren.

Das folgende Code-Snippet zeigt eine einfache Implementierung der Methode 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.
}

Benutzerdefinierte Suchaktionen

Eine einzelne benutzerdefinierte Suchaktion.

Abbildung 6 Benutzerdefinierte Suchaktion

Mit benutzerdefinierten Suchaktionen können Sie den MediaItem-Objekten Ihrer App in der Media-App des Autos benutzerdefinierte Symbole und Labels hinzufügen und Nutzerinteraktionen mit diesen Aktionen verarbeiten. So können Sie die Funktionen der Media App auf vielfältige Weise erweitern, z. B. durch die Aktionen „Herunterladen“, „Zur Wiedergabeliste hinzufügen“, „Radio abspielen“, „Zu Favoriten hinzufügen“ oder „Entfernen“.

Ein benutzerdefiniertes Dreipunkt-Menü für Suchaktionen.

Abbildung 7. Dreipunkt-Menü für benutzerdefinierte Suchaktion

Wenn mehr benutzerdefinierte Aktionen vorhanden sind, als der OEM anzeigen lässt, wird dem Nutzer ein Überlaufmenü angezeigt.

So funktioniert es:

Jede benutzerdefinierte Suchaktion wird mit folgenden Angaben definiert:

  • Eine Aktions-ID (eine eindeutige String-Kennung)
  • Ein Aktionslabel (der Text, der dem Nutzer angezeigt wird)
  • Ein URI für ein Aktionssymbol (ein vektorbasiertes Drawable, das eingefärbt werden kann)

Sie definieren eine Liste mit benutzerdefinierten Suchaktionen global als Teil Ihrer BrowseRoot. Anschließend können Sie eine Teilmenge dieser Aktionen einzelnen MediaItem.

Wenn ein Nutzer mit einer benutzerdefinierten Suchaktion interagiert, erhält Ihre App einen Callback in onCustomAction(). Sie können die Aktion dann bearbeiten und die Liste der Aktionen für die MediaItem bei Bedarf aktualisieren. Das ist nützlich für zustandsorientierte Aktionen wie „Zu Favoriten hinzufügen“ und „Herunterladen“. Für Aktionen, die nicht aktualisiert werden müssen, z. B. „Radio abspielen“, müssen Sie die Liste der Aktionen nicht aktualisieren.

Benutzerdefinierte Suchaktionen im Stammknoten eines Navigationsknotens.

Abbildung 8. Symbolleiste für benutzerdefinierte Suchaktionen

Sie können benutzerdefinierte Suchaktionen auch an den Stamm eines Navigationsknotens anhängen. Diese Aktionen werden in einer sekundären Symbolleiste unter der Hauptsymbolleiste angezeigt.

Benutzerdefinierte Suchaktionen implementieren

So fügen Sie Ihrem Projekt benutzerdefinierte Suchaktionen hinzu:

  1. Überschreiben Sie zwei Methoden in Ihrer MediaBrowserServiceCompat-Implementierung:
  2. Aktionslimits zur Laufzeit analysieren:
    • Rufe in onGetRoot() mit dem Schlüssel BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT in der rootHints Bundle die maximale Anzahl der zulässigen Aktionen für jede MediaItem ab. Ein Limit von 0 gibt an, dass die Funktion vom System nicht unterstützt wird.
  3. Erstellen Sie die globale Liste der benutzerdefinierten Suchaktionen:
    • Erstelle für jede Aktion ein Bundle-Objekt mit den folgenden Schlüsseln: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: Die Aktions-ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: Das Aktionslabel * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: Der URI des Aktionssymbols * Füge alle Bundle-Objekte der Aktion einer Liste hinzu.
  4. Fügen Sie die globale Liste Ihrem BrowseRoot hinzu:
  5. Fügen Sie Ihren MediaItem-Objekten Aktionen hinzu:
    • Du kannst einzelnen MediaItem-Objekten Aktionen hinzufügen, indem du die Liste der Aktions-IDs mit dem Schlüssel DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST in die MediaDescriptionCompat-Extras aufnimmst. Diese Liste muss eine Teilmenge der globalen Liste der Aktionen sein, die Sie in der BrowseRoot definiert haben.
  6. Aktionen verarbeiten und Fortschritt oder Ergebnisse zurückgeben:

Hier sind einige Änderungen, die Sie in Ihrer BrowserServiceCompat vornehmen können, um mit benutzerdefinierten Suchaktionen zu beginnen.

BrowserServiceCompat überschreiben

Sie müssen die folgenden Methoden in MediaBrowserServiceCompat überschreiben.

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Limit für Parseaktionen

Sie sollten prüfen, wie viele benutzerdefinierte Suchaktionen unterstützt werden.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Benutzerdefinierte Suchaktion erstellen

Jede Aktion muss in einem separaten Bundle verpackt werden.

  • Aktions-ID
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
    
  • Aktionslabel
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
    
  • URI für Aktionssymbol
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")
    

Benutzerdefinierte Suchaktionen zu Parceable ArrayList hinzufügen

Fügen Sie alle Objekte vom Typ „Benutzerdefinierte Suchaktion“ Bundle zu einem ArrayList hinzu.

private ArrayList<Bundle> createCustomActionsList(
                                        CustomBrowseAction browseActions) {
    ArrayList<Bundle> browseActionsBundle = new ArrayList<>();
    for (CustomBrowseAction browseAction : browseActions) {
        Bundle action = new Bundle();
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                browseAction.mId);
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                getString(browseAction.mLabelResId));
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                browseAction.mIcon);
        browseActionsBundle.add(action);
    }
    return browseActionsBundle;
}

Dem Stammverzeichnis für die Suche die Liste „Benutzerdefinierte Suchaktionen“ hinzufügen

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    Bundle browserRootExtras = new Bundle();
    browserRootExtras.putParcelableArrayList(
            BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
            createCustomActionsList()));
    mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
    return mRoot;
}

Aktionen zu einer MediaItem hinzufügen

MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                String description, Uri iconUri, Uri mediaUri,
                ArrayList<String> browseActionIds) {

    MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
    bob.setMediaId(id);
    bob.setTitle(title);
    bob.setSubtitle(subtitle);
    bob.setDescription(description);
    bob.setIconUri(iconUri);
    bob.setMediaUri(mediaUri);

    Bundle extras = new Bundle();
    extras.putStringArrayList(
          DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
          browseActionIds);

    bob.setExtras(extras);
    return bob.build();
}
MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

onCustomAction-Ergebnis erstellen

  • mediaId aus Bundle extras parsen:
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
    
  • Bei asynchronen Ergebnissen das Ergebnis trennen. result.detach()
  • Build-Ergebnis-Bundle
    • Nachricht an den Nutzer
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
      
    • Artikel aktualisieren(zum Aktualisieren von Aktionen in einem Artikel)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
      
    • Wiedergabeansicht öffnen
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
      
    • Knoten „Nach Knoten suchen“ aktualisieren
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
      
  • Bei einem Fehler result.sendError(resultBundle). anrufen
  • Wenn der Fortschritt aktualisiert wird, rufe result.sendProgressUpdate(resultBundle) auf.
  • Rufen Sie abschließend result.sendResult(resultBundle) an.

Aktionsstatus aktualisieren

Mit der Methode result.sendProgressUpdate(resultBundle) und dem Schlüssel EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM können Sie MediaItem aktualisieren, um den neuen Status der Aktion widerzuspiegeln. So können Sie den Nutzern in Echtzeit Feedback zum Fortschritt und Ergebnis ihrer Aktion geben.

Beispiel: Downloadaktion

Hier ein Beispiel dafür, wie Sie mit dieser Funktion eine Downloadaktion mit drei Status implementieren können:

  1. Herunterladen: Dies ist der Anfangsstatus der Aktion. Wenn der Nutzer diese Aktion auswählt, kannst du sie durch „Herunterladen“ ersetzen und sendProgressUpdate aufrufen, um die Benutzeroberfläche zu aktualisieren.
  2. Herunterladen: Dieser Status gibt an, dass der Download gerade ausgeführt wird. Sie können diesen Status verwenden, um dem Nutzer eine Fortschrittsanzeige oder einen anderen Indikator zu zeigen.
  3. Heruntergeladen: Dieser Status gibt an, dass der Download abgeschlossen ist. Wenn der Download abgeschlossen ist, können Sie „Herunterladen“ durch „Heruntergeladen“ ersetzen und sendResult mit der Taste EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um anzugeben, dass das Element aktualisiert werden soll. Außerdem kannst du den Schlüssel EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE verwenden, um dem Nutzer eine Erfolgsmeldung anzuzeigen.

So können Sie den Nutzern klares Feedback zum Downloadprozess und seinem aktuellen Status geben. Mit Symbolen können Sie noch mehr Details hinzufügen, um den Downloadstatus bei 25%, 50 % und 75% anzuzeigen.

Beispiel: Lieblingsaktion

Ein weiteres Beispiel ist eine Lieblingsaktion mit zwei Status:

  1. Favoriten: Diese Aktion wird für Elemente angezeigt, die nicht in der Favoritenliste des Nutzers enthalten sind. Wenn der Nutzer diese Aktion auswählt, kannst du sie durch „Zu Favoriten hinzugefügt“ ersetzen und sendResult mit der Taste EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um die Benutzeroberfläche zu aktualisieren.
  2. Zu den Favoriten hinzugefügt: Diese Aktion wird für Elemente angezeigt, die sich in der Favoritenliste des Nutzers befinden. Wenn der Nutzer diese Aktion auswählt, kannst du sie durch „Favoriten“ ersetzen und sendResult mit der Taste EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um die Benutzeroberfläche zu aktualisieren.

Dieser Ansatz bietet Nutzern eine klare und einheitliche Möglichkeit, ihre Lieblingselemente zu verwalten.

Diese Beispiele zeigen die Flexibilität benutzerdefinierter Suchaktionen und wie Sie damit eine Vielzahl von Funktionen mit Echtzeitfeedback implementieren können, um die Nutzerfreundlichkeit in der Medien-App des Autos zu verbessern.

Eine vollständige Beispielimplementierung dieser Funktion finden Sie im Projekt TestMediaApp.

Wiedergabesteuerung aktivieren

Android Auto und Android Automotive OS senden Befehle zur Wiedergabesteuerung über die MediaSessionCompat deines Dienstes. Sie müssen eine Sitzung registrieren und die zugehörigen Callback-Methoden implementieren.

Mediensitzung registrieren

Erstelle in der Methode onCreate() deines Media-Browser-Dienstes eine MediaSessionCompat und registriere die Mediensitzung durch Aufrufen von setSessionToken().

Das folgende Code-Snippet zeigt, wie eine Mediensitzung erstellt und registriert wird:

Kotlin

override fun onCreate() {
    super.onCreate()
    ...
    // Start a new MediaSession.
    val session = MediaSessionCompat(this, "session tag").apply {
        // Set a callback object that implements MediaSession.Callback
        // to handle play control requests.
        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 that implements MediaSession.Callback
    // to handle play control requests.
    session.setCallback(new MyMediaSessionCallback());
    ...
}

Wenn du das Mediensitzungsobjekt erstellst, legst du ein Callback-Objekt fest, das zum Verarbeiten von Anfragen zur Wiedergabesteuerung verwendet wird. Sie erstellen dieses Callback-Objekt, indem Sie eine Implementierung der Klasse MediaSessionCompat.Callback für Ihre App bereitstellen. Im nächsten Abschnitt wird beschrieben, wie Sie dieses Objekt implementieren.

Wiedergabebefehle implementieren

Wenn ein Nutzer die Wiedergabe eines Medienelements aus Ihrer App anfordert, verwenden Android Automotive OS und Android Auto die MediaSessionCompat.Callback-Klasse aus dem MediaSessionCompat-Objekt Ihrer App, das über den Media-Browser-Dienst Ihrer App abgerufen wurde. Wenn ein Nutzer die Wiedergabe von Inhalten steuern möchte, z. B. die Wiedergabe pausieren oder zum nächsten Titel springen, rufen Android Auto und Android Automotive OS eine der Methoden des Callback-Objekts auf.

Damit die Wiedergabe von Inhalten funktioniert, muss Ihre App die abstrakte Klasse MediaSessionCompat.Callback erweitern und die Methoden implementieren, die von Ihrer App unterstützt werden.

Implementiere alle folgenden Rückrufmethoden, die für die Art der Inhalte in deiner App sinnvoll sind:

onPrepare()
Wird aufgerufen, wenn sich die Medienquelle ändert. Android Automotive OS ruft diese Methode auch direkt nach dem Starten auf. Ihre Medien-App muss diese Methode implementieren.
onPlay()
Wird aufgerufen, wenn der Nutzer „Wiedergabe“ auswählt, ohne einen bestimmten Titel auszuwählen. Ihre App muss die Standardinhalte abspielen oder, wenn die Wiedergabe mit onPause() pausiert wurde, die Wiedergabe fortsetzen.

Hinweis:Ihre App sollte nicht automatisch mit der Wiedergabe von Musik beginnen, wenn Android Automotive OS oder Android Auto eine Verbindung zu Ihrem Medienbrowserdienst herstellt. Weitere Informationen finden Sie im Abschnitt zum Festlegen des anfänglichen Wiedergabestatus.

onPlayFromMediaId()
Wird aufgerufen, wenn der Nutzer einen bestimmten Artikel abspielen möchte. Der Methode wird die ID übergeben, die dein Media-Browser-Dienst dem Medienelement in deiner Inhaltshierarchie zugewiesen hat.
onPlayFromSearch()
Wird aufgerufen, wenn der Nutzer die Wiedergabe über eine Suchanfrage auswählt. Die App muss anhand des übergebenen Suchstrings eine geeignete Auswahl treffen.
onPause()
Wird aufgerufen, wenn der Nutzer die Wiedergabe pausiert.
onSkipToNext()
Wird aufgerufen, wenn der Nutzer zum nächsten Element springen möchte.
onSkipToPrevious()
Wird aufgerufen, wenn der Nutzer zum vorherigen Element springen möchte.
onStop()
Wird aufgerufen, wenn der Nutzer die Wiedergabe beendet.

Überschreiben Sie diese Methoden in Ihrer App, um die gewünschten Funktionen bereitzustellen. Sie müssen eine Methode nicht implementieren, wenn ihre Funktion von Ihrer App nicht unterstützt wird. Wenn Ihre App beispielsweise einen Livestream wie eine Sportübertragung abspielt, müssen Sie die Methode onSkipToNext() nicht implementieren. Sie können stattdessen die Standardimplementierung von onSkipToNext() verwenden.

Ihre App benötigt keine spezielle Logik, um Inhalte über die Lautsprecher des Autos abzuspielen. Wenn Ihre App eine Anfrage zum Abspielen von Inhalten erhält, kann sie Audioinhalte auf die gleiche Weise wiedergeben, wie sie Inhalte über die Lautsprecher oder Kopfhörer des Smartphones eines Nutzers wiedergibt. Android Auto und Android Automotive OS senden die Audioinhalte automatisch an das System des Autos, damit sie über die Lautsprecher des Autos wiedergegeben werden.

Weitere Informationen zur Wiedergabe von Audioinhalten finden Sie unter MediaPlayer – Übersicht, Audio-App – Übersicht und ExoPlayer – Übersicht.

Standardwiedergabeaktionen festlegen

In Android Auto und Android Automotive OS werden Wiedergabesteuerungen basierend auf den Aktionen angezeigt, die im PlaybackStateCompat-Objekt aktiviert sind.

Ihre App muss standardmäßig die folgenden Aktionen unterstützen:

Ihre App kann zusätzlich die folgenden Aktionen unterstützen, sofern sie für die Inhalte der App relevant sind:

Außerdem haben Sie die Möglichkeit, eine Wiedergabeliste zu erstellen, die dem Nutzer angezeigt werden kann. Dies ist jedoch nicht erforderlich. Rufen Sie dazu die Methoden setQueue() und setQueueTitle() auf, aktivieren Sie die Aktion ACTION_SKIP_TO_QUEUE_ITEM und definieren Sie den Callback onSkipToQueueItem().

Fügen Sie außerdem Unterstützung für das Symbol Now Playing hinzu, das angibt, was gerade wiedergegeben wird. Rufe dazu die Methode setActiveQueueItemId() auf und übergebe die ID des aktuell wiedergegebenen Elements in der Wiedergabeliste. Du musst setActiveQueueItemId() jedes Mal aktualisieren, wenn sich die Warteschlange ändert.

In Android Auto und Android Automotive OS werden Schaltflächen für jede aktivierte Aktion sowie die Wiedergabeliste angezeigt. Wenn auf die Schaltflächen geklickt wird, ruft das System den entsprechenden Rückruf von MediaSessionCompat.Callback auf.

Ungenutzte Speicherplatz reservieren

In Android Auto und Android Automotive OS wird Platz in der Benutzeroberfläche für die Aktionen ACTION_SKIP_TO_PREVIOUS und ACTION_SKIP_TO_NEXT reserviert. Wenn Ihre App eine dieser Funktionen nicht unterstützt, wird in Android Auto und Android Automotive OS der Bereich für benutzerdefinierte Aktionen verwendet, die Sie erstellen.

Wenn Sie diese Bereiche nicht mit benutzerdefinierten Aktionen füllen möchten, können Sie sie reservieren, damit Android Auto und Android Automotive OS den Bereich leer lassen, wenn Ihre App die entsprechende Funktion nicht unterstützt. Rufen Sie dazu die Methode setExtras() mit einem Extras-Bundle auf, das Konstanten enthält, die den reservierten Funktionen entsprechen. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT entspricht ACTION_SKIP_TO_NEXT und SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV entspricht ACTION_SKIP_TO_PREVIOUS. Verwenden Sie diese Konstanten als Schlüssel im Bundle und den booleschen Wert true für ihre Werte.

Anfangsstatus der Wiedergabe festlegen

Da Android Auto und Android Automotive OS mit Ihrem Medienbrowserdienst kommunizieren, gibt Ihre Mediensitzung den Status der Inhaltswiedergabe über das Symbol PlaybackStateCompat an. Ihre App sollte nicht automatisch mit der Musikwiedergabe beginnen, wenn Android Automotive OS oder Android Auto eine Verbindung zu Ihrem Medienbrowserdienst herstellt. Stattdessen kannst du Android Auto und Android Automotive OS verwenden, um die Wiedergabe basierend auf dem Status des Autos oder den Aktionen der Nutzer fortzusetzen oder zu starten.

Legen Sie dazu den ursprünglichen PlaybackStateCompat deiner Mediensitzung auf STATE_STOPPED, STATE_PAUSED, STATE_NONE oder STATE_ERROR fest.

Mediensitzungen in Android Auto und Android Automotive OS dauern nur so lange wie die Fahrt, sodass Nutzer diese Sitzungen häufig starten und beenden. Um einen reibungslosen Wechsel zwischen Laufwerken zu ermöglichen, sollte der vorherige Sitzungsstatus des Nutzers überwacht werden. Wenn die Medien-App eine Fortsetzungsanfrage erhält, kann der Nutzer automatisch dort weitermachen, wo er aufgehört hat, z. B. beim zuletzt abgespielten Medienelement, der PlaybackStateCompat und der Wiedergabeliste.

Benutzerdefinierte Wiedergabeaktionen hinzufügen

Sie können benutzerdefinierte Wiedergabeaktionen hinzufügen, um zusätzliche Aktionen anzuzeigen, die von Ihrer Medien-App unterstützt werden. Wenn der Platz ausreicht (und nicht reserviert ist), fügt Android den Steuerelementen für die Mobilitätsoptionen die benutzerdefinierten Aktionen hinzu. Andernfalls werden die benutzerdefinierten Aktionen im Dreipunkt-Menü angezeigt. Benutzerdefinierte Aktionen werden in der Reihenfolge angezeigt, in der sie dem PlaybackStateCompat hinzugefügt wurden.

Mit benutzerdefinierten Aktionen können Sie ein anderes Verhalten als bei Standardaktionen festlegen. Verwenden Sie sie nicht, um Standardaktionen zu ersetzen oder zu duplizieren.

Mit der Methode addCustomAction() in der Klasse PlaybackStateCompat.Builder können Sie benutzerdefinierte Aktionen hinzufügen.

Im folgenden Code-Snippet wird gezeigt, wie Sie eine benutzerdefinierte Aktion „Radiosender starten“ hinzufügen:

Kotlin

val customActionExtras = Bundle()
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO)

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

Java

Bundle customActionExtras = new Bundle();
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO);

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

Ein ausführlicheres Beispiel für diese Methode finden Sie in der Methode setCustomAction() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Nachdem du die benutzerdefinierte Aktion erstellt hast, kann deine Mediensitzung auf die Aktion reagieren, indem du die Methode onCustomAction() überschreibst.

Im folgenden Code-Snippet sehen Sie, wie Ihre App auf die Aktion „Radiosender starten“ reagieren könnte:

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)) {
        ...
    }
}

Ein ausführlicheres Beispiel für diese Methode finden Sie in der Methode onCustomAction in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Symbole für benutzerdefinierte Aktionen

Für jede benutzerdefinierte Aktion, die Sie erstellen, ist ein Symbol erforderlich.

Wenn die Beschreibung dieses Symbols mit einer der Konstanten CommandButton.ICON_ übereinstimmt, sollten Sie diesen Ganzzahlwert für den Schlüssel EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT der Extras der benutzerdefinierten Aktion festlegen. Auf unterstützten Systemen wird dadurch die Symbolressource überschrieben, die an CustomAction.Builder übergeben wird. So können Systemkomponenten deine Aktion und andere Wiedergabeaktionen in einem einheitlichen Stil rendern.

Sie müssen auch eine Symbolressource angeben. Apps in Autos können auf vielen verschiedenen Bildschirmgrößen und -dichten ausgeführt werden. Daher müssen die von Ihnen bereitgestellten Symbole Vektorgrafiken sein. Mit einem Vektor-Zeichnen können Sie Assets skalieren, ohne dass Details verloren gehen. Mit einem Vektorobjekt lassen sich außerdem bei geringeren Auflösungen die Kanten und Ecken an die Pixelgrenzen anpassen.

Wenn eine benutzerdefinierte Aktion zustandsabhängig ist, z. B. eine Wiedergabeeinstellung ein- oder ausschaltet, sollten Sie für die verschiedenen Status unterschiedliche Symbole verwenden, damit Nutzer eine Änderung sehen, wenn sie die Aktion auswählen.

Alternative Symbolstile für deaktivierte Aktionen bereitstellen

Wenn eine benutzerdefinierte Aktion für den aktuellen Kontext nicht verfügbar ist, ersetzen Sie das Symbol der benutzerdefinierten Aktion durch ein alternatives Symbol, das angibt, dass die Aktion deaktiviert ist.

Abbildung 6 Beispiele für benutzerdefinierte Aktionssymbole, die nicht dem Stil entsprechen.

Audioformat angeben

Wenn Sie angeben möchten, dass für die aktuell wiedergegebenen Medien ein spezielles Audioformat verwendet wird, können Sie Symbole angeben, die in Autos gerendert werden, die diese Funktion unterstützen. Du kannst KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI und KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI im Extras-Bundle des gerade wiedergegebenen Medienelements festlegen (an MediaSession.setMetadata() übergeben). Achte darauf, beide Extras festzulegen, um verschiedene Layouts zu berücksichtigen.

Außerdem kannst du das Extra KEY_IMMERSIVE_AUDIO festlegen, um OEMs von Autos zu informieren, dass es sich um immersive Audioinhalte handelt und sie sehr vorsichtig sein sollten, wenn sie Audioeffekte anwenden, die die immersiven Inhalte beeinträchtigen könnten.

Du kannst das aktuell wiedergegebene Medienelement so konfigurieren, dass sein Untertitel, seine Beschreibung oder beides Links zu anderen Medienelementen sind. So können Nutzer schnell zu ähnlichen Elementen springen, z. B. zu anderen Songs desselben Künstlers oder zu anderen Folgen dieses Podcasts. Wenn das Auto diese Funktion unterstützt, können Nutzer auf den Link tippen, um zu diesen Inhalten zu gelangen.

Wenn du Links hinzufügen möchtest, konfiguriere die Metadaten KEY_SUBTITLE_LINK_MEDIA_ID (für Links über den Untertitel) oder KEY_DESCRIPTION_LINK_MEDIA_ID (für Links über die Beschreibung). Weitere Informationen finden Sie in der Referenzdokumentation zu diesen Metadatenfeldern.

Sprachbefehle unterstützen

Ihre Medien-App muss Sprachaktionen unterstützen, um Fahrern eine sichere und praktische Nutzung zu ermöglichen, bei der sie nicht abgelenkt werden. Wenn in Ihrer App beispielsweise ein Medienelement wiedergegeben wird, kann der Nutzer „[Songtitel]abspielen“ sagen, um Ihrer App zu sagen, dass ein anderer Song abgespielt werden soll, ohne auf das Display des Autos zu schauen oder es zu berühren. Nutzer können Suchanfragen starten, indem sie auf die entsprechenden Tasten auf dem Lenkrad klicken oder das Hotword Hey Google sprechen.

Wenn Android Auto oder Android Automotive OS eine Sprachaktion erkennt und interpretiert, wird diese Sprachaktion über onPlayFromSearch() an die App gesendet. Wenn die App diesen Rückruf empfängt, sucht sie nach Inhalten, die mit dem String query übereinstimmen, und startet die Wiedergabe.

Nutzer können in ihrer Suchanfrage verschiedene Kategorien von Begriffen angeben, z. B. Genre, Künstler, Album, Songname, Radiosender oder Playlist. Berücksichtigen Sie beim Einrichten der Unterstützung für die Suche alle Kategorien, die für Ihre App sinnvoll sind. Wenn Android Auto oder Android Automotive OS erkennt, dass eine bestimmte Suchanfrage in bestimmte Kategorien fällt, werden dem Parameter extras Extras angehängt. Die folgenden Extras können gesendet werden:

Berücksichtigen Sie einen leeren query-String, der von Android Auto oder Android Automotive OS gesendet werden kann, wenn der Nutzer keine Suchbegriffe angibt. Beispiel: Der Nutzer sagt „Spiel Musik ab“. In diesem Fall wird in Ihrer App möglicherweise ein vor Kurzem abgespielter oder neu vorgeschlagener Titel gestartet.

Wenn eine Suche nicht schnell verarbeitet werden kann, blockiere sie nicht in onPlayFromSearch(). Lege stattdessen den Wiedergabestatus auf STATE_CONNECTING fest und führe die Suche in einem asynchronen Thread aus.

Nach Beginn der Wiedergabe kannst du die Wiedergabeliste der Mediensitzung mit ähnlichen Inhalten füllen. Wenn der Nutzer beispielsweise ein Album abspielen möchte, kann deine App die Wiedergabeliste mit der Titelliste des Albums füllen. Du solltest auch Suchergebnisse mit Suchansicht unterstützen, damit Nutzer einen anderen Titel auswählen können, der ihrer Suchanfrage entspricht.

Neben Suchanfragen vom Typ Wiedergabe erkennen Android Auto und Android Automotive OS Sprachanfragen zur Wiedergabesteuerung wie Musik pausieren und Nächster Titel und ordnen diese Befehle den entsprechenden Callbacks für die Mediensitzung zu, z. B. onPause() und onSkipToNext().

Ein detailliertes Beispiel für die Implementierung sprachaktivierter Wiedergabeaktionen in Ihrer App finden Sie unter Google Assistant und Medien-Apps.

Schutzmaßnahmen gegen Ablenkungen implementieren

Da das Smartphone eines Nutzers bei der Verwendung von Android Auto mit den Lautsprechern seines Autos verbunden ist, müssen Sie zusätzliche Vorkehrungen treffen, um Ablenkungen des Fahrers zu vermeiden.

Alarme im Auto unterdrücken

Android Auto-Medien-Apps dürfen Audioinhalte nicht über die Lautsprecher des Autos wiedergeben, es sei denn, der Nutzer startet die Wiedergabe, indem er beispielsweise auf eine Wiedergabeschaltfläche drückt. Auch ein vom Nutzer geplanter Wecker aus Ihrer Medien-App darf keine Musik über die Lautsprecher des Autos abspielen.

Um diese Anforderung zu erfüllen, kann Ihre App CarConnection als Signal verwenden, bevor Audio abgespielt wird. Ihre App kann prüfen, ob das Smartphone auf ein Autodisplay projiziert wird, indem sie den LiveData für den Typ der Verbindung zum Auto beobachtet und prüft, ob er mit CONNECTION_TYPE_PROJECTION übereinstimmt.

Wenn das Smartphone des Nutzers projiziert wird, müssen Medien-Apps, die Wecker unterstützen, eine der folgenden Aktionen ausführen:

  • Deaktivieren Sie den Wecker.
  • Der Wecker muss über STREAM_ALARM abgespielt werden und es muss eine Benutzeroberfläche auf dem Smartphone-Display geben, über die der Wecker deaktiviert werden kann.

Umgang mit Media-Anzeigen

Standardmäßig zeigt Android Auto eine Benachrichtigung an, wenn sich die Medienmetadaten während einer Audiowiedergabe ändern. Wenn eine Medien-App von der Musikwiedergabe zu einer Werbeunterbrechung wechselt, ist es ablenkend, dem Nutzer eine Benachrichtigung zu senden. Wenn du verhindern möchtest, dass Android Auto in diesem Fall eine Benachrichtigung anzeigt, musst du den Schlüssel für die Medienmetadaten METADATA_KEY_IS_ADVERTISEMENT auf METADATA_VALUE_ATTRIBUTE_PRESENT festlegen, wie im folgenden Code-Snippet gezeigt:

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 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 you normally would.
    mediaSession.setMetadata(builder.build());
}

Allgemeine Fehler behandeln

Wenn in der App ein Fehler auftritt, setze den Wiedergabestatus auf STATE_ERROR und gib mit der Methode setErrorMessage() eine Fehlermeldung an. Eine Liste der Fehlercodes, die Sie beim Festlegen der Fehlermeldung verwenden können, finden Sie unter PlaybackStateCompat. Fehlermeldungen müssen für Nutzer sichtbar sein und in der aktuellen Sprache des Nutzers lokalisiert sein. Android Auto und Android Automotive OS können dem Nutzer dann die Fehlermeldung anzeigen.

Wenn Inhalte beispielsweise in der aktuellen Region des Nutzers nicht verfügbar sind, kannst du den Fehlercode ERROR_CODE_NOT_AVAILABLE_IN_REGION verwenden, um die Fehlermeldung festzulegen.

Kotlin

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build())

Java

mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build());

Weitere Informationen zu Fehlerstatus findest du unter Mediasitzung verwenden: Status und Fehler.

Wenn ein Android Auto-Nutzer Ihre Smartphone-App öffnen muss, um einen Fehler zu beheben, teilen Sie ihm dies in Ihrer Nachricht mit. Die Fehlermeldung könnte beispielsweise „In [Name Ihrer App] anmelden“ statt „Bitte melden Sie sich an“ lauten.

Weitere Informationen