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 Komponenten von MediaBrowserService und MediaSession beschrieben, die deine App benötigt, damit sie unter Android Auto oder Android Automotive OS funktioniert. Nachdem du die zentrale Medieninfrastruktur fertiggestellt hast, kannst du deiner Medien-App die Unterstützung für Android Auto und 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 erkennen und ihre Inhalte anzuzeigen. Android Auto und Android Automotive OS verwenden einen Medienbrowser, um den Medienbrowserdienst deiner 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 steht für einen einzelnen Soundstream, 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. Das Element steht beispielsweise für ein Album und die untergeordneten Elemente sind die Songs 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 Android Automotive OS-Designrichtlinien entspricht. Die Benutzeroberfläche für diese Aktivitäten wird nicht von Android Automotive OS gezeichnet. Daher musst du dafür sorgen, dass deine 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 anhand der Informationen aus Ihrem Medienbrowserdienst eine eigene, für Fahrzeuge optimierte Oberfläche 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. Deklariere den Medienbrowserdienst in deinem Manifest, damit Android Auto und Android Automotive OS den Dienst finden und eine Verbindung zu deiner 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 kannst du angeben, dass du das Symbol deiner mobilen App zur Darstellung deiner Auto-Medien-App verwenden möchtest:

<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 Medienbrowserdienst auch verwenden, um anderen Clients den Zugriff auf Medieninhalte aus Ihrer App zu ermöglichen. Diese Medienclients 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-Browserdienst 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() Ihres Dienstes auf, um das Stammmedienelement in Ihrer 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 darüber, was das System auf dieser Ebene erwartet, finden Sie unter Stammmenü strukturieren auf dieser Seite.
  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 Callback-Methode für Mediensitzungen auf, um diese Aktion auszuführen.
  7. Wenn von Ihrer App unterstützt, kann der Nutzer auch in Ihren Inhalten suchen. In diesem Fall ruft 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 Methode onGetRoot() Ihres Dienstes gibt Informationen zum Stammknoten der Inhaltshierarchie zurück. Android Auto und Android Automotive OS verwenden diesen Root-Knoten, um den Rest deiner 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.

Paketvalidierung 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 Ihr Dienst entscheiden, ob das Paket auf Ihre Inhalte zugreifen kann. Du kannst beispielsweise den Zugriff auf den Inhalt deiner App auf eine Liste genehmigter Pakete beschränken. Dazu vergleichst du die clientPackageName mit deiner Zulassungsliste und bestätigst das Zertifikat, mit dem das APK des Pakets signiert wurde. Wenn das Paket nicht bestätigt werden kann, geben Sie null zurück, um den Zugriff auf Ihre Inhalte zu verweigern.

Damit System-Apps wie Android Auto und Android Automotive OS Zugriff auf deine Inhalte erhalten können, muss dein Dienst immer eine BrowserRoot ungleich null 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 dafür, wie Sie die Paketvalidierung für die Methode onGetRoot() Ihres Dienstes implementieren.

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, z. B. für 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 abspielen möchte, gibt sie das Token weiter. Ihre Anwendung 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 Stammhinweise 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. Das gilt insbesondere, wenn sich deine Hierarchie zwischen MediaBrowser-Integrationen außerhalb von Android Auto und Android Automotive OS unterscheidet. 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 Stammhinweisen gibt es 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. Bei kurzen Labels verringert sich die Wahrscheinlichkeit, dass 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(). Übergeben Sie bei MediaMetadataCompat-Objekten, die das gerade wiedergegebene Element darstellen, den URI mit einem der folgenden Schlüssel über putString():

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. Erstellen Sie 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üfen Sie in Ihrer Implementierung von ContentProvider.openFile(), ob für den entsprechenden URI eine Datei 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 Paket und suchen nach diesen Konstanten, um den passenden Stil zu bestimmen.

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. Dies entspricht den normalen Listenelementen, mit dem Unterschied, dass Ränder um die Symbole der Elemente herum angewendet werden, da die Symbole auf kleinen Werten 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 nur für blätterbare Elemente angezeigt.

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 bereits erwähnten Hinweise hinzu. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE gilt für die spielbaren untergeordneten Elemente dieses Objekts und DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE für die durchsuchbaren untergeordneten Elemente dieses Elements.

Wenn Sie die Standardeinstellung selbst und nicht für die untergeordneten Elemente überschreiben möchten, erstellen Sie ein Extras-Bundle im MediaDescription des Medienelements und fügen Sie 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 der 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, "Titel" und "Alben", in dieser Reihenfolge anzeigen und Ihre App übergibt fünf Medienelemente in der folgenden Reihenfolge:

  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 für die Gruppen „Songs“ und „Alben“ nicht in zusammenhängenden Blöcken zusammengehalten sind, werden dies von Android Auto und Android Automotive OS als die folgenden vier Gruppen interpretiert:

  • Gruppe 1 namens „Titel“, die Medienelement A enthalten
  • 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 Artikel 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-Beschreibungsextras 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 Indikatoren für ein Medienelement angezeigt werden sollen, das gerade abgespielt wird, kannst du in den MediaMetadataCompat deiner mediaSession Long-Werte für METADATA_KEY_IS_EXPLICIT oder EXTRA_DOWNLOAD_STATUS deklarieren. Die Anzeige DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS oder DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE kann nicht in der Wiedergabeansicht eingeblendet werden.

Das folgende Code-Snippet zeigt, wie angegeben wird, dass der aktuelle Titel in der Wiedergabeansicht explizit ist und heruntergeladen wurde:

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. Gibt ein Nutzer jedoch weiterhin die teilweise abgespielten Inhalte aus Android Auto oder Android Automotive OS wieder, wird diese Anzeige im Laufe 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 aus der onSearch()-Methode Ihres Dienstes mithilfe von title items (Titelelemente) organisieren, um sie besser durchsuchbar zu machen. 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. Dadurch können Sie die Funktionalität der Medien-App auf verschiedene Arten erweitern, z. B. durch Hinzufügen von Aktionen wie „Herunterladen“, „In die Wiedergabeliste“, „Radio abspielen“, „Favoriten“ oder „Entfernen“.

Ein Dreipunkt-Menü für benutzerdefinierte Suchaktionen.

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

Wenn mehr benutzerdefinierte Aktionen vorhanden sind, als vom OEM angezeigt werden dürfen, wird dem Nutzer ein Überlaufmenü angezeigt.

So funktioniert es:

Jede benutzerdefinierte Suchaktion wird wie folgt definiert:

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

Sie definieren eine Liste benutzerdefinierter 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:
    • Rufen Sie in onGetRoot() die maximal zulässige Anzahl von Aktionen für jede MediaItem mit dem Schlüssel BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT in rootHints Bundle 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:
    • Erstellen Sie für jede Aktion ein Bundle-Objekt mit den folgenden Schlüsseln: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: der Aktions-ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: dem Aktionslabel * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: dem Aktionssymbol-URI * Fügen Sie alle Bundle-Aktionsobjekte 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 einfügst. Diese Liste muss eine Teilmenge der globalen Liste von Aktionen sein, die Sie in 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 einer separaten Bundle zusammengefasst 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 von 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 „Browse“ 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 die Aktualisierung voranschreitet, rufen Sie 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 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 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 Ihres 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 Sie das Mediensitzungsobjekt erstellen, legen Sie ein Callback-Objekt fest, das zur Verarbeitung 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 deiner App anfordert, verwenden Android Automotive OS und Android Auto die Klasse MediaSessionCompat.Callback aus dem MediaSessionCompat-Objekt deiner App, die sie vom Medienbrowserdienst deiner App abgerufen haben. Wenn ein Nutzer die Wiedergabe von Inhalten steuern möchte, also z. B. die Wiedergabe pausieren oder zum nächsten Titel springen möchte, 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 von Ihrer App unterstützten Methoden implementieren.

Implementiere alle folgenden Callback-Methoden, die für den Inhaltstyp deiner App sinnvoll sind:

onPrepare()
Wird aufgerufen, wenn sich die Medienquelle ändert. Android Automotive OS ruft diese Methode ebenfalls sofort nach dem Booten auf. Diese Methode muss in Ihrer Medien-App implementiert sein.
onPlay()
Wird aufgerufen, wenn der Nutzer „Wiedergabe“ auswählt, ohne einen bestimmten Titel auszuwählen. Ihre App muss die Standardinhalte wiedergeben. Wenn die Wiedergabe mit onPause() pausiert wurde, wird sie fortgesetzt.

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 sich für eine Suchanfrage entscheidet. 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 wiederzugeben. 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, um sie über die Lautsprecher des Autos wiederzugeben.

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

Standardmäßige Wiedergabeaktionen festlegen

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

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

Deine App kann außerdem die folgenden Aktionen unterstützen, wenn sie für den Inhalt 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 Flächen reservieren

In Android Auto und Android Automotive OS wird in der Benutzeroberfläche Platz für die Aktionen ACTION_SKIP_TO_PREVIOUS und ACTION_SKIP_TO_NEXT reserviert. Wenn deine App eine dieser Funktionen nicht unterstützt, verwenden Android Auto und Android Automotive OS den Bereich, um von dir erstellte benutzerdefinierte Aktionen aufzurufen.

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 Wiedergabe von Inhalten ü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 die Dauer der Fahrt. Daher starten und beenden Nutzer diese Sitzungen häufig. 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

Du kannst benutzerdefinierte Wiedergabeaktionen hinzufügen, um zusätzliche Aktionen anzuzeigen, die von deiner 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 lässt sich ein Verhalten festlegen, das sich von Standardaktionen unterscheidet. 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

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

Ein ausführlicheres Beispiel für diese Methode finden Sie in der setCustomAction()-Methode 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 eine Symbolressource erforderlich. 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 zustandsorientiert ist, z. B. durch Ein- oder Ausschalten einer Wiedergabeeinstellung, werden verschiedene Symbole für die verschiedenen Status angezeigt, damit Nutzer eine Änderung sehen können, 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 in der gewohnten Form gezeigt werden.

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. Sie können KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI und KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI im Extras-Bundle des aktuell wiedergegebenen Medienelements festlegen (das an MediaSession.setMetadata() übergeben wurde). Sie müssen beide Extras festlegen, um unterschiedliche 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 Ihre App aufzufordern, einen anderen Song abzuspielen, 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 unterschiedliche Kategorien von Begriffen angeben, z. B. Genre, Künstler, Album, Titelname, 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 die Wiedergabe eines Albums anfordert, kann die Warteschlange von Ihrer App mit der Titelliste des Albums gefüllt werden. 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 auch 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 während der Nutzung von Android Auto mit den Lautsprechern des Autos verbunden ist, musst du zusätzliche Vorkehrungen treffen, um den Fahrer nicht abzulenken.

Alarme im Auto unterdrücken

Android Auto-Medien-Apps dürfen die Audiowiedergabe über die Autolautsprecher nur starten, wenn der Nutzer die Wiedergabe beispielsweise durch Drücken einer Wiedergabetaste startet. 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, einen der folgenden Schritte ausführen:

  • Deaktiviere den Alarm.
  • 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. Damit Android Auto in diesem Fall keine Benachrichtigung anzeigt, müssen Sie den Medienmetadatenschlüssel METADATA_KEY_IS_ADVERTISEMENT auf METADATA_VALUE_ATTRIBUTE_PRESENT setzen, 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, wenn du die Fehlermeldung festlegst.

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