构建内容层次结构

Android Auto 和 Android Automotive OS (AAOS) 调用应用的媒体浏览器服务,以发现哪些内容可用。为了支持这一点,您需要在媒体浏览器服务中实现这两种方法。

实现 onGetRoot

服务的 onGetRoot 方法返回有关内容层次结构根节点的信息。Android Auto 和 AAOS 使用此根节点,通过 onLoadChildren 方法请求您的其余内容。此代码段展示了 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);
}

如需查看此方法的详细示例,请参阅 GitHub 上 Universal Android Music Player 示例应用中的 onGetRoot

添加了软件包验证

调用服务的 onGetRoot 方法时,发出调用的软件包会将标识信息传递给您的服务。您的服务可以使用此信息来决定该软件包能否访问您的内容。

例如,您可以限制只有获批的软件包有权访问应用的内容:

  • clientPackageName 与您的许可名单进行比较。
  • 验证用于为软件包的 APK 签名的证书。

如果无法验证软件包,则返回 null 以拒绝它访问您的内容。

为了允许系统应用(例如 Android Auto 和 AAOS)访问您的内容,当这些系统应用调用 onGetRoot 方法时,您的服务必须返回一个非 null BrowserRoot

AAOS 系统应用的签名因汽车的品牌和型号而异。请务必允许来自所有系统应用的连接,以支持 AAOS。

此代码段展示了您的服务如何验证发出调用的软件包是否为系统应用:

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
}

此代码段摘录自 GitHub 上 Universal Android Music Player 示例应用中的 PackageValidator 类。请参阅该类,通过更详细的示例来了解如何为服务的 onGetRoot 方法实现软件包验证。

除了允许系统应用之外,您还必须允许 Google 助理连接到您的 MediaBrowserService。在手机(包括 Android Auto)和 Android Automotive OS 上,Google 助理使用单独的软件包名称

实现 onLoadChildren

收到根节点对象后,Android Auto 和 AAOS 会对根节点对象调用 onLoadChildren 来获取其后代,以构建顶级菜单。客户端应用使用后代节点对象调用同一方法来构建子菜单。

内容层次结构中的每个节点都由一个 MediaBrowserCompat.MediaItem 对象表示。其中每一个媒体项都由一个唯一的 ID 字符串标识。客户端应用将这些 ID 字符串视为不透明令牌。

当客户端应用想要浏览子菜单或播放媒体项时,它会传递这个令牌。您的应用负责将令牌与相应的媒体项关联。

此代码段展示了 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&lt;MediaBrowserCompat.MediaItem> = mutableListOf()

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

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

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

Java

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

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

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

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

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

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

如需查看此方法的示例,请参阅 GitHub 上 Universal Android Music Player 示例应用中的 onLoadChildren

构造根菜单

Android Auto 和 Android Automotive OS 对根菜单的结构有特定的约束条件。这些约束条件通过根提示传达给 MediaBrowserService,而根提示可通过传入 onGetRoot() 的 Bundle 实参来读取。遵循这些提示可让系统将根内容显示为导航标签页。如果您不遵循这些提示,某些根内容可能会被系统丢弃或不太容易被系统发现。

根内容显示为导航标签页

图 1. 根内容显示为导航标签页。

应用这些提示后,系统会将根内容显示为导航标签页。如果不应用这些提示,某些根内容可能会被丢弃或不太容易被发现。这些提示会传输:

Kotlin

import androidx.media.utils.MediaConstants

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

您可以根据这些提示的值选择为内容层次结构的逻辑创建分支,尤其是当您的层次结构因 Android Auto 和 AAOS 之外的 MediaBrowser 集成而异时。

例如,如果您通常显示一个根可播放项,根据受支持标志提示的值,您可能希望将其嵌套在一个根可浏览项下。

除了根提示之外,还应遵循以下准则,以确保标签页以最佳方式呈现:

  • 每个标签页项的单色(最好是白色)图标

  • 每个标签页项的简短而有意义的标签(简短的标签可降低标签被截断的可能性)