Medien-Apps für Autos entwickeln

Mit Android Auto und Android Automotive OS können Sie Ihre Medien-App-Inhalte Nutzern in ihrem 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 deine Inhalte finden und anzeigen können.

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

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

Vorbereitung

  1. Lesen Sie die 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 wichtigsten Begriffe und Konzepte an.

Schlüsselbegriffe und -konzepte

Medienbrowserdienst
Ein Android-Dienst, der von Ihrer Medien-App implementiert wird und der MediaBrowserServiceCompat API entspricht. Deine App verwendet diesen Dienst, um den Inhalt freizugeben.
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 deiner App zu finden.
Medienelement

Der Medienbrowser organisiert seinen Inhalt in einer Baumstruktur aus MediaItem-Objekten. Ein Medienelement kann eines oder beide der folgenden Flags haben:

  • FLAG_PLAYABLE: Gibt an, dass das Element ein Blatt in der Inhaltsstruktur ist. Das Element steht für einen einzelnen Audiostream, 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 in der Inhaltsstruktur ist und untergeordnete Elemente hat. Das Element stellt beispielsweise ein Album dar und die untergeordneten Elemente sind die Titel auf dem Album.

Ein Medienelement, das sowohl blätterbar als auch abspielbar ist, fungiert wie eine Playlist. Sie können das Element selbst auswählen, um alle untergeordneten Elemente wiederzugeben, 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. Achten Sie deshalb darauf, dass Ihre App den Designrichtlinien entspricht. Dazu gehören in der Regel größere Tippziele und Schriftgrößen, die Unterstützung von Tag- und Nachtmodi und höhere Kontrastverhältnisse.

Fahrzeugoptimierte Benutzeroberflächen dürfen nur angezeigt werden, wenn die Einschränkungen für die Fahrzeugnutzung (Auto User Experience Restrictions, CUXR) nicht aktiv sind, da diese Benutzeroberflächen möglicherweise eine längere Aufmerksamkeit oder eine umfassendere Interaktion des Nutzers erfordern. CUXRs sind nicht aktiv, wenn das Auto angehalten oder geparkt ist, aber immer aktiv, 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 deiner App konfigurieren

Bevor Sie den Medienbrowserdienst 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 nach Medienelementen zu suchen. Sie müssen Ihren Medienbrowserdienst in Ihrem Manifest deklarieren, damit Android Auto und Android Automotive OS den Dienst erkennen und eine Verbindung zu Ihrer App herstellen können.

Das folgende Code-Snippet zeigt, wie Sie Ihren Medienbrowserdienst in Ihrem Manifest deklarieren. Füge diesen Code in die Manifestdatei für dein Android Automotive OS-Modul und in die Manifestdatei für deine Telefon-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 festlegen

Du musst App-Symbole angeben, mit denen Android Auto und Android Automotive OS deine App in der System-UI darstellen können. Es sind zwei Symboltypen erforderlich:

  • Launcher-Symbol
  • Attributionssymbol

Launcher-Symbol

Das Launcher-Symbol steht für deine App in der System-UI, z. B. im Launcher und in der Symbolablage. Mit der folgenden Manifestdeklaration kannst du angeben, dass das Symbol aus deiner mobilen App zur Darstellung deiner Auto-Medien-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 im Manifest des Medienbrowserdienstes das Attribut android:icon im Element <service> des Medienbrowsers 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 auch das kleine Symbol für Benachrichtigungen wiederverwenden. Dieses Symbol muss einfarbig sein. Mit der folgenden Manifestdeklaration können Sie ein Symbol angeben, das Ihre App repräsentiert:

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

Medienbrowserdienst erstellen

Zum Erstellen eines Medienbrowserdienstes erweitern Sie die Klasse MediaBrowserServiceCompat. Sowohl Android Auto als auch Android Automotive OS können Ihren Dienst dann für Folgendes verwenden:

  • Durchsuchen Sie die Inhaltshierarchie Ihrer App, um dem Nutzer ein Menü anzuzeigen.
  • Rufen Sie das Token für das Objekt MediaSessionCompat Ihrer App ab, um die Audiowiedergabe zu steuern.

Sie können den Medienbrowserdienst auch verwenden, damit andere Clients über Ihre Anwendung auf Medieninhalte zugreifen können. Diese Medienclients können andere Anwendungen 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 Medienbrowserdienst interagieren.

  1. Der Nutzer startet deine App unter Android Automotive OS oder Android Auto.
  2. Android Automotive OS oder Android Auto kontaktiert den Medienbrowserdienst Ihrer App mithilfe der Methode onCreate(). Bei der Implementierung der Methode onCreate() musst du ein MediaSessionCompat-Objekt und dessen 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. Es wird stattdessen verwendet, um weitere Inhalte aus Ihrer App abzurufen.
  4. Android Automotive OS oder Android Auto ruft die Methode onLoadChildren() Ihres Dienstes auf, um die untergeordneten Elemente des Stammmedienelements abzurufen. Bei Android Automotive OS und Android Auto werden diese Medienelemente als oberste Ebene von Inhaltselementen angezeigt. Weitere Informationen dazu, was das System auf dieser Ebene erwartet, finden Sie unter Stammmenü strukturieren auf dieser Seite.
  5. Wenn der Nutzer ein durchsuchbares Medienelement auswählt, wird die Methode onLoadChildren() des Dienstes noch einmal aufgerufen, um die untergeordneten Elemente des ausgewählten Menüelements abzurufen.
  6. Wenn der Nutzer ein abspielbares Medienelement auswählt, ruft Android Automotive OS oder Android Auto die entsprechende Callback-Methode für die Mediensitzung auf, um diese Aktion auszuführen.
  7. Sofern dies von Ihrer App unterstützt wird, können 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. Sie müssen in Ihrem Medienbrowserdienst zwei Methoden implementieren, um dies zu unterstützen: 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 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 findest du in der Methode onGetRoot() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Paketvalidierung für onGetRoot() hinzufügen

Bei einem Aufruf der Methode onGetRoot() Ihres Dienstes übergibt das aufrufende Paket identifizierende Informationen an Ihren Dienst. Ihr Dienst kann diese Informationen verwenden, um zu entscheiden, ob dieses Paket auf Ihre Inhalte zugreifen kann. Du kannst beispielsweise den Zugriff auf die Inhalte deiner App auf eine Liste genehmigter Pakete beschränken. Dazu vergleichst du clientPackageName mit deiner Zulassungsliste und prüfst das Zertifikat, mit dem das APK des Pakets signiert wurde. Wenn das Paket nicht verifiziert werden kann, gib null zurück, um den Zugriff auf deine Inhalte zu verweigern.

Damit System-Apps wie Android Auto und Android Automotive OS Zugriff auf Ihre Inhalte haben, muss Ihr Dienst immer einen 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. Daher musst du Verbindungen von allen System-Apps zulassen, um Android Automotive OS stabil zu unterstützen.

Das folgende Code-Snippet zeigt, wie Ihr Dienst prüfen kann, ob das aufrufende Paket eine Systemanwendung ist:

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 Beispiel-App „Universal Android Music Player“ auf GitHub. In dieser Klasse finden Sie ein detaillierteres Beispiel für die Implementierung der Paketvalidierung für die Methode onGetRoot() Ihres Dienstes.

Neben System-Apps musst du auch zulassen, dass Google Assistant eine Verbindung zu deinem MediaBrowserService herstellt. Google Assistant hat separate Paketnamen für das Smartphone. Dazu gehören Android Auto und Android Automotive OS.

onLoadChildren() implementieren

Nach dem Empfang des Stammknotenobjekts erstellen Android Auto und Android Automotive OS ein Menü der obersten Ebene. Dazu rufen Sie onLoadChildren() für das Stammknotenobjekt auf, um die untergeordneten Elemente abzurufen. Client-Apps erstellen Untermenüs, indem sie dieselbe Methode mit untergeordneten Knotenobjekten aufrufen.

Jeder Knoten in Ihrer Contenthierarchie 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 zu einem Untermenü wechseln oder ein Medienelement abspielen möchte, übergibt sie das Token. Ihre 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 findest du in der Methode onLoadChildren() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Stammmenü strukturieren

Abbildung 2: Stamminhalt, der als Navigationstabs angezeigt wird.

Bei Android Auto und Android Automotive OS gelten für die Struktur des Hauptmenüs spezielle Einschränkungen. Diese werden über Stammhinweise an MediaBrowserService übermittelt, die durch das Bundle-Argument gelesen werden können, das an onGetRoot() übergeben wird. Wenn Sie diese Hinweise befolgen, kann das System den Stamminhalt optimal als Navigationstabs anzeigen. Wenn Sie diese Hinweise nicht beachten, kann es passieren, dass Root-Inhalte verworfen oder vom System weniger gut sichtbar gemacht werden. 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 sich deine Hierarchie zwischen MediaBrowser-Integrationen außerhalb von Android Auto und Android Automotive OS unterscheidet. Wenn Sie beispielsweise normalerweise ein abspielbares Stammelement anzeigen, sollten Sie es aufgrund des Hinweises zu unterstützten Flags stattdessen unter einem durchsuchbaren Stammelement verschachteln.

Neben den Root-Hinweisen gibt es einige weitere Richtlinien, die du beachten solltest, damit Tabs optimal dargestellt werden:

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

Artwork für Medien anzeigen

Artwork für Medienelemente muss als lokaler URI mit ContentResolver.SCHEME_CONTENT oder ContentResolver.SCHEME_ANDROID_RESOURCE übergeben werden. Dieser lokale URI muss entweder in eine Bitmap oder ein Vektor-Drawable in den Ressourcen der Anwendung aufgelöst werden. Bei MediaDescriptionCompat-Objekten, die Elemente in der Inhaltshierarchie darstellen, übergeben Sie den URI über setIconUri(). Übergeben Sie bei MediaMetadataCompat-Objekten, die das aktuell wiedergegebene Element darstellen, den URI über putString() mit einem der folgenden Schlüssel:

In den folgenden Schritten wird beschrieben, wie Sie Grafiken aus einem Web-URI herunterladen und über einen lokalen URI verfügbar machen. Ein ausführlicheres Beispiel findest du in der Implementierung von openFile() und den umgebenden Methoden in der Beispiel-App für den Universal Android Music Player.

  1. Erstellen Sie einen content://-URI, der dem Web-URI entspricht. Der Medienbrowserdienst und die Mediensitzung übergeben diesen Inhalts-URI an Android Auto und Android Automotive OS.

    Kotlin

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

    Java

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
    
  2. Prüfen Sie in der Implementierung von ContentProvider.openFile(), ob für den entsprechenden URI eine Datei vorhanden ist. Ist dies nicht der Fall, 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 Contentanbietern finden Sie unter Contentanbieter erstellen.

Inhaltsstile anwenden

Nachdem Sie eine Inhaltshierarchie mit durchsuchbaren oder spielbaren Elementen erstellt haben, können Sie Inhaltsstile anwenden, die bestimmen, 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 werden Bilder gegenüber Titeln und Metadaten priorisiert.

Standardstile für Inhalte festlegen

Sie können globale Standardeinstellungen für die Anzeige Ihrer Mediaelemente festlegen, indem Sie im Extras-Bundle BrowserRoot der Methode onGetRoot() Ihres Dienstes bestimmte Konstanten angeben. Android Auto und Android Automotive OS lesen dieses Bundle und suchen nach diesen Konstanten, um den passenden Stil zu bestimmen.

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

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

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: Die entsprechenden Elemente werden als Listenelemente angezeigt.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: Die entsprechenden Elemente werden als Rasterelemente angezeigt.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: Die entsprechenden Elemente werden als Listenelemente vom Typ „Kategorie“ angezeigt. Sie sind dieselben wie normale Listenelemente, mit der Ausnahme, dass Ränder um die Symbole der Elemente herum angewendet werden, da die Symbole besser aussehen, wenn sie klein sind. Bei den Symbolen muss es sich um farbfähige Vektor-Drawables handeln. Dieser Hinweis ist normalerweise nur für durchsuchbare Elemente enthalten.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: Die entsprechenden Elemente werden als Kategorie-Rasterelemente angezeigt. Sie sind die gleichen wie herkömmliche Rasterelemente, mit dem Unterschied, dass Ränder um die Symbole der Elemente herum angewendet werden, da die Symbole besser aussehen, wenn sie klein sind. Bei den Symbolen muss es sich um farbfähige Vektor-Drawables handeln. Dieser Hinweis ist normalerweise nur für durchsuchbare Elemente enthalten.

Das folgende Code-Snippet zeigt, wie Sie als Standardinhaltsstil für durchsuchbare Elemente Raster und 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);
}

Inhaltsformate pro Element festlegen

Mit der Content Style API können Sie den Standardinhaltsstil sowohl für die untergeordneten Elemente aller durchsuchbaren Medienelemente als auch für jedes Medienelement selbst überschreiben.

Wenn Sie den Standardwert für die untergeordneten Elemente eines durchsuchbaren Medienelements überschreiben möchten, erstellen Sie im MediaDescription des Medienelements ein zusätzliches Set und fügen Sie dieselben zuvor genannten Hinweise hinzu. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE gilt für die spielbaren Kinder dieses Elements, während DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE für die blätterbaren untergeordneten Elemente dieses Elements gilt.

Wenn Sie den Standardwert für ein bestimmtes Medienelement selbst und nicht für seine untergeordneten Elemente überschreiben möchten, erstellen Sie im MediaDescription des Medienelements ein Set mit Extras und fügen einen Hinweis mit dem Schlüssel DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM hinzu. Verwenden Sie dieselben Werte wie zuvor beschrieben, um die Darstellung dieses Elements anzugeben.

Das folgende Code-Snippet zeigt, wie Sie ein durchsuchbares MediaItem erstellen, das den Standardinhaltsstil sowohl für sich selbst als auch für seine untergeordneten Elemente überschreibt. Er wird als Kategorielistenelement gestaltet, seine durchsuchbaren untergeordneten Elemente als Listenelemente und ihre spielbaren untergeordneten Elemente als Rasterelemente:

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

Um ähnliche Medienelemente zu gruppieren, verwenden Sie einen Hinweis pro Element. Jedes Medienelement in einer Gruppe muss in seinem MediaDescription ein Extras-Set deklarieren, 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.

Das folgende Code-Snippet zeigt, wie Sie ein MediaItem mit der Untergruppe "Songs" als Überschrift erstellen:

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

Ihre App muss alle Medienelemente übergeben, die Sie als zusammenhängenden Block gruppieren möchten. Angenommen, Sie möchten zwei Gruppen von Medienelementen "Songs" 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 werden, interpretieren Android Auto und Android Automotive OS dies als die folgenden vier Gruppen:

  • Gruppe 1 namens „Titel“, die Medienelement A enthält
  • Gruppe 2 namens „Alben“, die Medienelement B enthält
  • Gruppe 3 namens „Titel“ mit den Medienelementen C und D
  • Gruppe 4 namens „Alben“, die das Medienelement E enthalten

Um diese Elemente in zwei Gruppen anzuzeigen, 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 Metadatenanzeigen anzeigen

Sie können zusätzliche Metadatenanzeigen einfügen, um auf einen Blick Informationen zu Inhalten in der Baumstruktur des Medienbrowsers und während der Wiedergabe bereitzustellen. Innerhalb des Navigationsbaums 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 der Mediensitzung und suchen nach bestimmten Konstanten, um anzuzeigende Indikatoren zu bestimmen.

Abbildung 3: Wiedergabeansicht mit Metadaten zur Angabe des Titels und Künstlers sowie einem Symbol für explizite Inhalte.

Abbildung 4: Suchansicht mit einem Punkt für nicht wiedergegebene Inhalte auf dem ersten Element und einer Fortschrittsanzeige für teilweise wiedergegebene Inhalte auf dem zweiten Element.

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

Die folgenden Konstanten können nur in den Beschreibungsextras für MediaItem verwendet werden:

Wenn Indikatoren angezeigt werden sollen, während der Nutzer die Media-Suche durchstöbert, müssen Sie ein zusätzliches Set erstellen, das eine oder mehrere dieser Konstanten enthält, und dieses Set an die Methode MediaDescription.Builder.setExtras() übergeben.

Das folgende Code-Snippet zeigt, wie Indikatoren für ein explizites Medienelement angezeigt werden, das zu 70% abgeschlossen 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 */);

Damit Indikatoren für ein Medienelement eingeblendet werden, das gerade wiedergegeben wird, können Sie Long-Werte für METADATA_KEY_IS_EXPLICIT oder EXTRA_DOWNLOAD_STATUS in MediaMetadataCompat Ihres mediaSession deklarieren. Die Anzeigen DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS oder DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE können in der Wiedergabeansicht nicht eingeblendet werden.

Das folgende Code-Snippet zeigt, wie Sie angeben, 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 Suchansicht aktualisieren, während Inhalte wiedergegeben werden

Wie bereits erwähnt, kannst du mit DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE eine Fortschrittsanzeige für teilweise wiedergegebene Inhalte in der Übersicht einblenden. Wenn ein Nutzer jedoch den teilweise wiedergegebenen Inhalt aus Android Auto oder Android Automotive OS weiterhin 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 in MediaMetadataCompat und PlaybackStateCompat zusätzliche Informationen angeben, um aktuelle Inhalte mit Medienelementen in der Suchansicht zu verknüpfen. Die folgenden Anforderungen müssen erfüllt sein, damit für das Medienelement eine Fortschrittsanzeige für automatische Updates angezeigt wird:

Das folgende Code-Snippet zeigt, wie Sie angeben, dass das aktuell wiedergegebene Element mit einem Element in der Suchansicht 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“ für die Anzeige von Medienelementen, die zur Sprachsuche des Nutzers passen.

Ihre Anwendung kann kontextbezogene Suchergebnisse bereitstellen, die Nutzern angezeigt werden, wenn sie eine Suchanfrage starten. Android Auto und Android Automotive OS zeigen diese Ergebnisse über Suchabfrageoberflächen oder Angebote an, die auf Suchanfragen zu einem früheren Zeitpunkt in der Sitzung ausgerichtet sind. Weitere Informationen finden Sie in diesem Leitfaden im Abschnitt Sprachbefehle unterstützen.

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

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

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

Damit Suchergebnisse angezeigt werden können, müssen Sie die Methode onSearch() in Ihrem Medienbrowserdienst überschreiben. Android Auto und Android Automotive OS leiten die Suchbegriffe der Nutzer an diese Methode weiter, wenn ein Nutzer eine Suchanfrageoberfläche oder „Suchergebnisse“ aufruft.

Sie können die Suchergebnisse der onSearch()-Methode Ihres Dienstes mithilfe von Titelelementen organisieren, damit sie leichter durchsucht werden können. Wenn in Ihrer App beispielsweise Musik wiedergegeben wird, 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: Einzelne benutzerdefinierte Suchaktion

Mit benutzerdefinierten Suchaktionen kannst du den MediaItem-Objekten deiner App in der Medien-App des Autos benutzerdefinierte Symbole und Labels hinzufügen und Nutzerinteraktionen mit diesen Aktionen verarbeiten. So können Sie die Funktionalität der Medien-App auf verschiedene Arten erweitern und beispielsweise Aktionen wie „Herunterladen“, „Zur Wiedergabeliste hinzufügen“, „Radio hören“, „Favorit“ oder „Entfernen“ hinzufügen.

Ein Dreipunkt-Menü für benutzerdefinierte Suchaktionen.

Abbildung 7: Überlauf benutzerdefinierter Suchaktionen

Wenn es mehr benutzerdefinierte Aktionen gibt, als der OEM zulässt, wird dem Nutzer ein Dreipunkt-Menü angezeigt.

So funktioniert es:

Jede benutzerdefinierte Suchaktion wird definiert durch:

  • Eine Aktions-ID (eine eindeutige String-ID)
  • Ein Aktionslabel (der dem Nutzer angezeigte Text)
  • Ein Aktionssymbol-URI (ein Vektor-Drawable, das eingefärbt werden kann)

Im Rahmen von BrowseRoot definieren Sie global eine Liste von benutzerdefinierten Suchaktionen. Anschließend können Sie eine Teilmenge dieser Aktionen an einzelne MediaItem. anhängen

Wenn ein Nutzer mit einer benutzerdefinierten Suchaktion interagiert, erhält Ihre App einen Callback in onCustomAction(). Anschließend können Sie die Aktion verarbeiten und bei Bedarf die Liste der Aktionen für die MediaItem aktualisieren. Dies ist nützlich für zustandsorientierte Aktionen wie „Favorit“ und „Download“. Bei Aktionen wie „Radio hören“, die nicht aktualisiert werden müssen, müssen Sie die Liste der Aktionen nicht aktualisieren.

Benutzerdefinierte Suchaktionen in einem Stammknoten zum Durchsuchen.

Abbildung 8: Symbolleiste für benutzerdefinierte Suchaktionen

Sie können benutzerdefinierte Suchaktionen auch an einen Suchknotenstamm 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. Hier können Sie zwei Methoden in der MediaBrowserServiceCompat-Implementierung überschreiben:
  2. Parsen Sie die Aktionslimits zur Laufzeit:
    • In onGetRoot() wird 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 abgerufen. Ein Grenzwert von 0 bedeutet, 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: Aktions-ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: Aktionslabel * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: URI des Aktionssymbols * Alle Bundle-Objekte für die Aktion einer Liste hinzufügen
  4. Fügen Sie die globale Liste zu BrowseRoot hinzu:
  5. So fügen Sie Ihren MediaItem-Objekten Aktionen hinzu:
    • Sie können einzelnen MediaItem-Objekten Aktionen hinzufügen, indem Sie die Liste der Aktions-IDs mit dem Schlüssel DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST in die MediaDescriptionCompat-Extras aufnehmen. Diese Liste muss eine Teilmenge der globalen Liste von Aktionen sein, die Sie in der BrowseRoot definiert haben.
  6. So verarbeiten Sie Aktionen und geben den Fortschritt oder Ergebnisse zurück:

Hier sind einige Änderungen, die Sie in 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 Parse-Aktionen

Sie sollten überprü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 eine separate 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>")
    
  • Aktionssymbol-URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")
    

Benutzerdefinierte Suchaktionen zu Parceable ArrayList hinzufügen

Füge alle Bundle-Objekte für benutzerdefinierte Suchaktionen zu einer 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;
}

Liste der benutzerdefinierten Suchaktionen dem Suchstamm 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 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

  • Media-ID 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 trennen Sie das Ergebnis. result.detach()
  • Ergebnis-Bundle erstellen
    • Nachricht an Nutzer
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
      
    • Element aktualisieren(zum Aktualisieren von Aktionen in einem Element)
      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);
      
    • Suchknoten aktualisieren
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
      
  • Bei einem Fehler result.sendError(resultBundle). aufrufen
  • Wenn der Fortschritt aktualisiert wird, rufe result.sendProgressUpdate(resultBundle) an.
  • Rufe zum Abschluss result.sendResult(resultBundle) auf.

Aktionsstatus aktualisieren

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

Beispiel: Download-Aktion

Hier ist ein Beispiel dafür, wie Sie diese Funktion verwenden können, um eine Downloadaktion mit drei Status zu implementieren:

  1. Herunterladen: Dies ist der Anfangszustand der Aktion. Wenn der Nutzer diese Aktion auswählt, können Sie sie durch „Wird heruntergeladen“ ersetzen und sendProgressUpdate aufrufen, um die UI zu aktualisieren.
  2. Wird heruntergeladen: Dieser Status zeigt an, dass der Download gerade durchgeführt wird. Sie können diesen Status verwenden, um dem Nutzer einen Fortschrittsbalken oder eine andere Anzeige anzuzeigen.
  3. Heruntergeladen: Dieser Status zeigt an, dass der Download abgeschlossen ist. Wenn der Download abgeschlossen ist, können Sie „Wird heruntergeladen“ durch „Heruntergeladen“ ersetzen und sendResult mit dem Schlüssel EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um anzugeben, dass das Element aktualisiert werden soll. Außerdem können Sie den Schlüssel EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE verwenden, um dem Nutzer eine Erfolgsmeldung anzuzeigen.

Auf diese Weise können Sie dem Nutzer klares Feedback zum Downloadvorgang und zu seinem aktuellen Status geben. Mit Symbolen kannst du den Downloadstatus von 25%, 50 % und 75% anzeigen.

Beispiel: Lieblingsaktion

Ein weiteres Beispiel ist eine beliebte Aktion mit zwei Status:

  1. Favorit: Diese Aktion wird für Elemente angezeigt, die nicht in der Favoritenliste des Nutzers enthalten sind. Wenn der Nutzer diese Aktion auswählt, können Sie sie durch „Zu Favoriten hinzufügen“ ersetzen und sendResult mit dem Schlüssel EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um die UI 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, können Sie sie durch „Favorit“ ersetzen und sendResult mit der Taste EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM aufrufen, um die UI zu aktualisieren.

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

Diese Beispiele zeigen die Flexibilität von benutzerdefinierten Suchaktionen und wie du sie verwenden kannst, um eine Vielzahl von Funktionen mit Echtzeit-Feedback zu implementieren, um eine verbesserte Nutzererfahrung in der Medien-App des Autos zu ermöglichen.

Ein vollständiges Beispiel für die Implementierung dieser Funktion finden Sie im Projekt TestMediaApp.

Wiedergabesteuerung aktivieren

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

Mediensitzung registrieren

Erstellen Sie in der Methode onCreate() Ihres Medienbrowserdienstes einen MediaSessionCompat und registrieren Sie dann 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, lege ein Callback-Objekt fest, das zur Verarbeitung von Anfragen zur Wiedergabesteuerung verwendet wird. Zum Erstellen dieses Callback-Objekts geben Sie eine Implementierung der Klasse MediaSessionCompat.Callback für Ihre App an. Im nächsten Abschnitt wird die Implementierung dieses Objekts erläutert.

Wiedergabebefehle implementieren

Wenn ein Nutzer die Wiedergabe eines Medienelements aus Ihrer App anfordert, verwenden Android Automotive OS und Android Auto die Klasse MediaSessionCompat.Callback aus dem MediaSessionCompat-Objekt Ihrer App, das sie vom Medienbrowserdienst Ihrer App abgerufen haben. Wenn ein Nutzer die Inhaltswiedergabe 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.

Für die Wiedergabe von Inhalten muss die App die abstrakte MediaSessionCompat.Callback-Klasse erweitern und die von der App unterstützten Methoden implementieren.

Implementieren Sie die folgenden Callback-Methoden, die für den Inhaltstyp Ihrer App sinnvoll sind:

onPrepare()
Wird aufgerufen, wenn die Medienquelle geändert wird. Android Automotive OS ruft diese Methode auch unmittelbar nach dem Start auf. Ihre Medien-App muss diese Methode implementieren.
onPlay()
Wird aufgerufen, wenn der Nutzer die Wiedergabe startet, ohne ein bestimmtes Element auszuwählen. Ihre App muss die Standardinhalte wiedergeben. Wenn sie mit onPause() pausiert wurde, wird sie stattdessen fortgesetzt.

Hinweis:In Ihrer App sollte die Musikwiedergabe nicht automatisch gestartet werden, wenn Android Automotive OS oder Android Auto eine Verbindung zu Ihrem Medienbrowserdienst herstellt. Weitere Informationen findest du im Abschnitt zum Festlegen des anfänglichen Wiedergabestatus.

onPlayFromMediaId()
Wird aufgerufen, wenn der Nutzer ein bestimmtes Element abspielen möchte. Der Methode wird die ID übergeben, die dem Medienelement in Ihrer Inhaltshierarchie von Ihrem Medienbrowserdienst zugewiesen wurde.
onPlayFromSearch()
Wird aufgerufen, wenn der Nutzer über eine Suchanfrage Inhalte abspielen möchte. Die Anwendung muss auf Grundlage des übergebenen Suchstrings eine geeignete Auswahl treffen.
onPause()
Wird aufgerufen, wenn der Nutzer die Wiedergabe pausiert.
onSkipToNext()
Wird aufgerufen, wenn der Nutzer mit dem nächsten Element springt.
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 keine Methode implementieren, wenn ihre Funktionalität nicht von Ihrer App unterstützt wird. Wenn Ihre App beispielsweise einen Livestream (z. B. eine Sportübertragung) wiedergibt, 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 Inhalte über die Lautsprecher oder Kopfhörer eines Smartphones eines Nutzers. Android Auto und Android Automotive OS senden die Audioinhalte automatisch an das System des Autos, um es über die Lautsprecher des Autos wiederzugeben.

Weitere Informationen zum Abspielen von Audioinhalten finden Sie unter MediaPlayer, Übersicht über die Audio-App und ExoPlayer-Übersicht.

Standard-Wiedergabeaktionen festlegen

In Android Auto und Android Automotive OS werden Steuerelemente für die Wiedergabe auf Grundlage der Aktionen angezeigt, die im PlaybackStateCompat-Objekt aktiviert sind.

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

Ihre App kann außerdem die folgenden Aktionen unterstützen, wenn sie für den Inhalt der App relevant sind:

Darüber hinaus haben Sie die Möglichkeit, eine Warteschlange zu erstellen, die dem Nutzer angezeigt werden kann, aber nicht unbedingt erforderlich ist. Rufen Sie dazu die Methoden setQueue() und setQueueTitle() auf, aktivieren Sie die Aktion ACTION_SKIP_TO_QUEUE_ITEM und definieren Sie den Callback onSkipToQueueItem().

Außerdem soll das Symbol Läuft gerade unterstützt werden. Damit wird angegeben, was gerade läuft. Rufen Sie dazu die Methode setActiveQueueItemId() auf und übergeben Sie die ID des aktuell wiedergegebenen Elements in der Warteschlange. Sie müssen setActiveQueueItemId() bei jeder Änderung der Warteschlange aktualisieren.

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

Ungenutzten Platz reservieren

Android Auto und Android Automotive OS reservieren in der UI Platz für die Aktionen ACTION_SKIP_TO_PREVIOUS und ACTION_SKIP_TO_NEXT. Wenn deine App eine dieser Funktionen nicht unterstützt, verwenden Android Auto und Android Automotive OS den Bereich, um von dir erstellte benutzerdefinierte Aktionen anzuzeigen.

Wenn du diese Bereiche nicht mit benutzerdefinierten Aktionen füllen möchtest, kannst du sie reservieren, sodass Android Auto und Android Automotive OS den Bereich leer lassen, wenn deine App die entsprechende Funktion nicht unterstützt. Rufen Sie dazu die Methode setExtras() mit einem zusätzlichen Bundle auf, das Konstanten für die reservierten Funktionen enthält. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT entspricht ACTION_SKIP_TO_NEXT und SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV für ACTION_SKIP_TO_PREVIOUS. Verwenden Sie diese Konstanten als Schlüssel im Bundle und den booleschen Wert true für die zugehörigen Werte.

Anfänglichen Wiedergabestatus festlegen

Während Android Auto und Android Automotive OS mit Ihrem Medienbrowserdienst kommunizieren, sendet Ihre Mediensitzung den Status der Inhaltswiedergabe über das PlaybackStateCompat. Ihre App sollte nicht automatisch mit der Musikwiedergabe beginnen, wenn Android Automotive OS oder Android Auto eine Verbindung zu Ihrem Medienbrowserdienst herstellt. Setzen Sie stattdessen Android Auto und Android Automotive OS ein, um die Wiedergabe je nach Zustand des Autos oder Nutzeraktionen fortzusetzen oder zu starten.

Legen Sie dazu die anfängliche PlaybackStateCompat Ihrer Mediensitzung auf STATE_STOPPED, STATE_PAUSED, STATE_NONE oder STATE_ERROR fest.

Mediensitzungen in Android Auto und Android Automotive OS dauern nur für die Dauer der Fahrt, sodass Nutzer diese Sitzungen häufig starten und beenden. Für eine nahtlose Nutzung zwischen den Laufwerken sollten Sie den vorherigen Sitzungsstatus des Nutzers im Auge behalten. Wenn die Medien-App eine Anfrage zum Fortsetzen erhält, kann der Nutzer dann automatisch dort weitermachen, wo er aufgehört hat, z. B. beim zuletzt wiedergegebenen Medienelement, dem PlaybackStateCompat und der Warteschlange.

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 es zulässt (und nicht reserviert) ist, fügt Android die benutzerdefinierten Aktionen den Transportsteuerelementen hinzu. Andernfalls werden die benutzerdefinierten Aktionen im Dreipunkt-Menü angezeigt. Benutzerdefinierte Aktionen werden in der Reihenfolge angezeigt, in der sie zu PlaybackStateCompat hinzugefügt wurden.

Mit benutzerdefinierten Aktionen kannst du ein Verhalten erzielen, 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.

Das folgende Code-Snippet zeigt, wie Sie die benutzerdefinierte Aktion „Start a radio channel“ (Radiokanal 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 findest du in der Methode setCustomAction() in der Beispiel-App „Universal Android Music Player“ auf GitHub.

Nachdem Sie die benutzerdefinierte Aktion erstellt haben, kann Ihre Mediensitzung auf die Aktion reagieren, indem die Methode onCustomAction() überschrieben wird.

Das folgende Code-Snippet zeigt, wie Ihre App auf die Aktion „Start a radio channel“ (Radiokanal 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 findest du 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. Die von dir bereitgestellten Symbole müssen also Vektor-Drawables sein. Mit einem Vektor-Drawable können Sie Assets skalieren, ohne Details zu verlieren. Ein Vektor-Drawable erleichtert außerdem das Ausrichten der Kanten und Ecken an Pixelgrenzen bei kleineren Auflösungen.

Wenn eine benutzerdefinierte Aktion zustandsorientiert ist, z. B. durch das Ein- oder Ausschalten einer Wiedergabeeinstellung, können Sie unterschiedliche Symbole für die verschiedenen Status bereitstellen. So sehen Nutzer eine Änderung, 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, tauschen Sie das Symbol für die benutzerdefinierte Aktion durch ein alternatives Symbol aus, das anzeigt, dass die Aktion deaktiviert ist.

Abbildung 6: Beispiele für benutzerdefinierte Aktionssymbole, die nichts mit dem Designstil zu tun haben.

Audioformat angeben

Wenn Sie angeben möchten, dass aktuell Medien ein spezielles Audioformat verwenden, 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-Set des aktuell wiedergegebenen Medienelements festlegen, das an MediaSession.setMetadata() übergeben wird. Achten Sie darauf, beide Extras für verschiedene Layouts festzulegen.

Darüber hinaus können Sie das zusätzliche KEY_IMMERSIVE_AUDIO-Objekt festlegen, um Auto-OEMs darüber zu informieren, dass es sich um immersives Audio handelt. Diese sollten sehr vorsichtig sein, wenn sie entscheiden, ob sie Audioeffekte anwenden, die die immersiven Inhalte beeinträchtigen könnten.

Sie können 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 wechseln, z. B. zu anderen Songs desselben Künstlers, anderen Folgen dieses Podcasts usw. Wenn das Auto diese Funktion unterstützt, können Nutzer auf den Link tippen, um diese Inhalte aufzurufen.

Wenn du Links hinzufügen möchtest, konfiguriere die KEY_SUBTITLE_LINK_MEDIA_ID-Metadaten (für die Verknüpfung mit dem Untertitel) oder KEY_DESCRIPTION_LINK_MEDIA_ID für eine Verknüpfung von der Beschreibung aus. Weitere Informationen finden Sie in der Referenzdokumentation zu diesen Metadatenfeldern.

Sprachbefehle unterstützen

Ihre Medien-App muss Sprachbedienung unterstützen, damit Fahrer sicher und nutzerfreundlich sind und Ablenkungen minimiert werden. Wenn in Ihrer App beispielsweise ein Medienelement abgespielt wird, kann der Nutzer Spiel [Titel des Titels] abspielen. Dadurch wird die App angewiesen, einen anderen Titel abzuspielen, ohne das Display des Autos zu sehen oder zu berühren. Nutzer können Suchanfragen starten, indem sie auf die entsprechenden Schaltflächen am Lenkrad klicken oder die Hotwords Ok Google sagen.

Wenn Android Auto oder Android Automotive OS eine Spracheingabe erkennt und interpretiert, wird diese Sprachbedienung über onPlayFromSearch() an die App gesendet. Beim Empfang dieses Callbacks sucht die App nach Inhalten, die mit dem String query übereinstimmen, und startet die Wiedergabe.

Nutzer können in ihrer Suchanfrage verschiedene Kategorien von Begriffen angeben: Genre, Künstler, Album, Songname, Radiosender oder Playlist. Berücksichtigen Sie bei der Entwicklung der Suchfunktion alle Kategorien, die für Ihre App sinnvoll sind. Wenn Android Auto oder Android Automotive OS erkennt, dass eine bestimmte Abfrage in bestimmte Kategorien fällt, werden Extras an den extras-Parameter angehängt. Die folgenden Extras können gesendet werden:

Geben Sie einen leeren query-String an, der von Android Auto oder Android Automotive OS gesendet werden kann, wenn der Nutzer keine Suchbegriffe angibt. Beispiel: „Spiel Musik“. In diesem Fall kann Ihre App einen kürzlich abgespielten oder neu vorgeschlagenen Titel starten.

Wenn eine Suche nicht schnell verarbeitet werden kann, sollten Sie in onPlayFromSearch() nicht blockieren. Setze stattdessen den Wiedergabestatus auf STATE_CONNECTING und führe die Suche in einem asynchronen Thread durch.

Sobald die Wiedergabe beginnt, sollten Sie die Warteschlange der Mediensitzung mit ähnlichen Inhalten füllen. Wenn der Nutzer beispielsweise das Abspielen eines Albums anfordert, füllt Ihre App die Warteschlange möglicherweise mit der Trackliste des Albums. Sie können auch die Unterstützung für durchsuchbare Suchergebnisse implementieren, damit Nutzer einen anderen Track auswählen können, der ihrer Suchanfrage entspricht.

Zusätzlich zu play-Anfragen erkennen Android Auto und Android Automotive OS Sprachbefehle wie „Musik pausieren“ und „Nächster Titel“ zur Steuerung der Wiedergabe und ordnen diese Befehle den entsprechenden Callbacks für Mediensitzungen wie onPause() und onSkipToNext() zu.

Ein ausführliches Beispiel zum Implementieren sprachgesteuerter Wiedergabeaktionen in deiner App findest du unter Google Assistant und Medien-Apps.

Ablenkungsvorkehrungen treffen

Da das Smartphone eines Nutzers während der Verwendung von Android Auto mit den Lautsprechern seines Autos verbunden ist, müssen Sie zusätzliche Maßnahmen ergreifen, um zu verhindern, dass der Fahrer abgelenkt wird.

Alarme im Auto unterdrücken

Android Auto-Medien-Apps dürfen die Audiowiedergabe über die Autolautsprecher erst starten, wenn der Nutzer die Wiedergabe beispielsweise durch Drücken einer Wiedergabetaste startet. Auch bei einem vom Nutzer eingestellten Wecker über Ihre Medien-App darf keine Musik über die Autolautsprecher abgespielt werden.

Damit diese Anforderung erfüllt wird, kann Ihre App vor der Audiowiedergabe CarConnection als Signal verwenden. Ihre App kann prüfen, ob das Smartphone auf den Bildschirm eines Autos übertragen wird. Dazu wird LiveData als Autoverbindungstyp beobachtet und geprüft, ob er CONNECTION_TYPE_PROJECTION entspricht.

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

  • Den Alarm ausschalten.
  • Alarm über STREAM_ALARM abspielen und eine Benutzeroberfläche auf dem Smartphone-Display bereitstellen, um den Alarm zu deaktivieren.

Umgang mit Medienwerbung

Standardmäßig zeigt Android Auto eine Benachrichtigung an, wenn sich die Medienmetadaten während einer Audiowiedergabesitzung ändern. Wenn eine Medien-App von der Wiedergabe von Musik zu einer Werbeanzeige wechselt, lenkt sie von der Anzeige einer Benachrichtigung für den Nutzer ab. Damit Android Auto in diesem Fall keine Benachrichtigung anzeigt, musst du 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 mithilfe der Methode setErrorMessage() eine Fehlermeldung an. Unter PlaybackStateCompat finden Sie eine Liste von Fehlercodes, die Sie beim Festlegen der Fehlermeldung verwenden können. Fehlermeldungen müssen für den Nutzer sichtbar sein und der aktuellen Sprache des Nutzers entsprechen. Android Auto und Android Automotive OS können dem Nutzer die Fehlermeldung dann anzeigen.

Wenn der Inhalt beispielsweise in der aktuellen Region des Nutzers nicht verfügbar ist, können Sie beim Festlegen der Fehlermeldung den Fehlercode ERROR_CODE_NOT_AVAILABLE_IN_REGION verwenden.

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 finden Sie unter Mediensitzung verwenden: Status und Fehler.

Wenn ein Android Auto-Nutzer deine Smartphone-App öffnen muss, um einen Fehler zu beheben, gib diese Informationen in deiner Nachricht an den Nutzer weiter. Ihre Fehlermeldung könnte beispielsweise „Melde dich in [Name deiner App]“ anstelle von „Bitte melde dich an“ lauten.

Weitere Informationen