mcpField
A
android-for-cars-parked-apps

android-for-cars-parked-apps

github.com/android开发10.0k199 次安装2 天前更新
代码仓库

关于 android-for-cars-parked-apps

指导 Android 编码 Agent 将现有视频、游戏或浏览器应用适配为 Android Auto 或 Android Automotive OS 的驻车应用。内容涵盖平台清单、车载界面、安全要求与发布检查。

主要功能

  1. 01识别驻车应用类别与目标平台
  2. 02配置清单和车载兼容性
  3. 03处理返回导航与屏幕方向
  4. 04适配系统栏和不规则显示区域
  5. 05暂停行驶期间的媒体播放

适用场景

  1. 01将视频应用适配至车载系统
  2. 02调整游戏以支持驻车场景
  3. 03为车载浏览器落实数据保护要求

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/android/skills --skill android-for-cars-parked-apps

安装后在 Claude Code 中输入/android-for-cars-parked-apps确认已加载。

附带文件references/android/docs/quality-guidelines/car-app-quality/index.md

详细介绍

将现有 Android 视频应用、游戏和网页浏览器改造为可在 Android Auto 和 Android Automotive OS 上运行的驻车应用。按照以下步骤操作,以满足 Google Play 分发的汽车应用质量准则。

前提条件

  • 从现有的自适应 Android 应用开始,且该应用属于受支持的驻车应用类别之一:视频流应用、游戏或网页浏览器。

术语表

  • 驻车应用:驻车应用仅在车辆停放时于 Android Auto 和 Android Automotive OS 上运行。驻车应用分为视频、游戏和浏览器三类;其他类别的应用不能作为驻车应用发布。
  • Android Auto:Android Auto 是由手机驱动的投射系统。应用在用户的手机上运行,并通过有线或无线连接投射到车载显示屏上。Android Auto 是与 Android Automotive OS 不同的平台。
  • Android Automotive OS:Android Automotive OS(简称 AAOS)是直接运行在车辆上的 Android 变体。Android Automotive OS 是与 Android Auto 不同的平台。
  • 视频类别:视频应用可在车辆停放时播放流媒体视频。通常可在 Android Automotive OS 上使用;Android Auto 不支持。
  • 游戏类别:游戏应用可在车辆停放时提供游戏体验。通常可在 Android Automotive OS 和 Android Auto 上使用。
  • 浏览器类别:网页浏览器可让用户在车辆停放时浏览网页。目前在 Android Automotive OS 上以 Beta 版提供;Android Auto 不支持。
  • 媒体类别:非驻车类别。媒体应用可在驾驶或停车时播放音频内容(音乐、广播、有声读物,不包括视频)。
  • Android for Cars 平台:Android for Cars 是一个统称,包含 Android Auto 和 Android Automotive OS。
  • 通常可用的类别:对于受支持的平台,可在 Google Play 的内部测试、封闭式测试、公开测试和正式版轨道中发布。
  • Beta 类别:可在内部测试或封闭式测试轨道中发布;公开测试和正式版轨道则必须获准加入抢先体验计划。
  • 驾驶时播放音频:Android Automotive OS 上的一项 Beta 功能,可让视频应用在车辆行驶时播放音频。
  • 汽车应用质量准则:根据第 3 级(适用于汽车)、第 2 级(针对汽车优化)或第 1 级(汽车特色应用)标准审核应用时,必须检查汽车应用质量准则。
  • Android Automotive OS 兼容模式:兼容模式(android.software.car.display_compatibility)是部分车型上的一项系统功能,可提供系统返回操作提示、安全区域渲染、密度缩放,以及行驶模式下的遮挡(将活动移至 Stopped 状态)。兼容模式仅适用于支持 Android Automotive OS 的应用;仅支持 Android Auto 的应用无需考虑此模式。

探索代码库

在代码库中搜索 AndroidManifest.xml(Unity 项目中也可创建 Assets/Plugins/Android/AndroidManifest.xml)、res/xml/automotive_app_desc.xml、build.gradle、build.gradle.kts 或 gradle/libs.versions.toml 文件。查找现有的视频播放器、MediaSession 类、requestedOrientation 调用,以及 WindowInsets 处理程序。除非用户另有指示,否则添加依赖项前请先征求许可。

第 1 步:配置应用清单

在 AndroidManifest.xml 中声明应用类别、目标平台、硬件功能要求和 Activity 配置。

1.1:识别应用类别
  • 视频应用:视频应用必须在 <application> 上设置 android:appCategory="video",并且不得包含带有 uses name="video" 的 app/src/main/res/xml/automotive_app_desc.xml。
  • 游戏:游戏必须在 <application> 上设置 android:appCategory="game"。如果应用支持控制器输入,请声明 android.hardware.gamepad,并设置 android:required="false"。
  • 浏览器:浏览器必须在已导出的 <activity>(android:exported="true")的 MAIN <intent-filter> 中包含 <category android:name="android.intent.category.APP_BROWSER"/>。
1.2:识别目标平台和硬件功能
  • Android Auto:面向 Android Auto(Android 15+)的应用必须在 AndroidManifest.xml 文件的某个 <intent-filter> 中添加 <category android:name="android.intent.category.CAR_LAUNCHER"/>。除非用户指定其他 intent filter,否则将其添加到 MAIN <intent-filter> 中。如果应用仅面向 Android Automotive OS,绝不要添加此类别。
  • Android Automotive OS:面向 Android Automotive OS 的应用必须在 AndroidManifest.xml 文件中声明 <uses-feature android:name="android.hardware.type.automotive" android:required="false"/>(适用于移动设备或多形态设备渠道时设为 "false";仅当应用将在专用 Automotive OS 渠道发布时才设为 "true")。如果应用仅面向 Android Auto,绝不要添加此功能标签。
  • 非必需硬件功能(DO-1):为满足 Google Play 功能要求和 DO-1,请将不支持的功能(android.hardware.wifi、android.hardware.screen.portrait、android.hardware.screen.landscape、android.hardware.camera)声明为 android:required="false",并移除车载设备不具备的功能上的 android:required="true"。
  • 显示连续性(LS-C1):在 <activity> 上为 screenSize、smallestScreenSize、orientation、screenLayout 和 density 配置 android:configChanges,以避免切换到远端显示屏或从远端显示屏切换回来时重建 activity 或导致播放卡顿。
  • 兼容模式和驾驶分心:声明兼容模式是可选的,且仅适用于支持 Android Automotive OS 的应用。对于仅支持 Android Auto 的应用,绝不要添加兼容模式。添加 android.hardware.type.automotive 会禁用兼容模式,除非在 <application> 中添加 <meta-data android:name="android.software.car.display_compatibility" android:value="true"/>。当应用在 AndroidManifest.xml 中声明 android.software.car.display_compatibility 时,应用必须在运行时检查 PackageManager.hasSystemFeature("android.software.car.display_compatibility"),并在结果为 true 时隐藏应用内重复的返回或关闭控件。驻车应用不得在任何 <activity> 上包含 <meta-data android:name="distractionOptimized" android:value="true"/>(DD-2、DD-3)。

第 2 步:提供返回导航和方向支持

2.1:添加应用内返回和关闭控件

Android Automotive OS 车辆不一定配备硬件或软件返回按钮,也不一定支持手势导航(AN-1)。请在每个非根屏幕(包括详情屏幕和全屏播放器)上提供明确的 UI 返回和关闭控件:

  • 如果 AndroidManifest.xml 未声明 android.software.car.display_compatibility:始终显示明确的应用内返回和关闭控件。
  • 如果 AndroidManifest.xml 声明了 android.software.car.display_compatibility,且其 android:value="true":仅当 PackageManager.hasSystemFeature("android.software.car.display_compatibility") 返回 false 时,才显示应用内返回和关闭控件。
2.2:防护运行时方向请求

车载显示屏的方向是固定的。在固定为纵向的车载显示屏上请求横向显示,会导致黑边或闪烁循环(DO-1)。删除硬编码的 android:screenOrientation 清单属性,并在设置 requestedOrientation 前检查 PackageManager.FEATURE_SCREEN_LANDSCAPE 和 PackageManager.FEATURE_SCREEN_PORTRAIT。

步骤 3:适配系统栏和不规则显示屏挖孔

3.1:跟踪可控制的系统栏内边距

在 Android Automotive OS 上,OEM 可以锁定系统栏使其保持可见,以便用户随时使用空调和车辆控制功能(AR-1)。使用 OnControllableInsetsChangedListener 跟踪可控制的内边距,仅隐藏可控制的系统栏,并对不可控制的 statusBars、navigationBars 和 displayCutout 内边距应用 windowInsetsPadding。

3.2:安全地在不规则显示屏挖孔区域渲染内容

为满足 Tier 1 标准 AR-2,在曲面或非矩形车载显示屏上,绝不要使用 LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT(0)或 LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES(1)。使用 car 资源限定符,在 res/values-car-v30/integers.xml 中配置 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS(3)或 LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER(2):

<integer name="windowLayoutInDisplayCutoutMode">3</integer>

使用 WindowInsets.safeDrawing 保护交互式 UI、应用栏和可滚动列表。

步骤 4:满足驾驶员分心和媒体播放要求

4.1:在生命周期转换时暂停播放,并禁止车辆行驶时恢复播放

车辆开始移动时,操作系统会使用阻挡层覆盖已停放的 Activity,并触发 onPause(在兼容模式车辆上还会触发 onStop)。为满足 DD-2、DD-3 和 MC-1:

  1. 使用来自 androidx.lifecycle:lifecycle-runtime-compose 的 LifecycleEventEffect,在 ON_PAUSE 或 ON_STOP 时暂停播放。
  2. 暴露带有播放、暂停或停止命令及媒体项元数据(MC-1)的 MediaSession(androidx.media3:media3-session),并在 onCleared 中释放它。
  3. 在 build.gradle.kts 中添加 useLibrary("android.car"),将 ExoPlayer 包装在 ForwardingSimpleBasePlayer 中,监听 CarUxRestrictionsManager,在 isRequiresDistractionOptimization 变为 true 时暂停播放,并在 getState 中移除 COMMAND_PLAY_PAUSE,以防止外部 MediaSession 控件在车辆行驶时恢复播放:
@UnstableApi
class CarAwarePlayer(context: Context) :
    ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
    private var shouldPreventPlay = false
    private var pausedByUxRestrictions = false
    private lateinit var carUxRestrictionsManager: CarUxRestrictionsManager

    init {
        if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
            val car = Car.createCar(context)
            carUxRestrictionsManager =
                car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager
            shouldPreventPlay =
                carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
            invalidateState()

            carUxRestrictionsManager.registerListener { restrictions: CarUxRestrictions ->
                shouldPreventPlay = restrictions.isRequiresDistractionOptimization
                if (!shouldPreventPlay && pausedByUxRestrictions) {
                    handleSetPlayWhenReady(true)
                    invalidateState()
                } else if (shouldPreventPlay && isPlaying) {
                    pausedByUxRestrictions = true
                    handleSetPlayWhenReady(false)
                    invalidateState()
                }
            }
        }
    }

    override fun getState(): State {
        val state = super.getState()
        return state.buildUpon()
            .setAvailableCommands(
                state.availableCommands.buildUpon()
                    .removeIf(COMMAND_PLAY_PAUSE, shouldPreventPlay)
                    .build()
            )
            .build()
    }

    override fun handleRelease(): ListenableFuture<*> {
        if (::carUxRestrictionsManager.isInitialized) {
            carUxRestrictionsManager.unregisterListener()
        }
        return super.handleRelease()
    }
}

4.2:支持车辆行驶时的后台音频(可选 Beta 功能)

由于这是可选的 Beta 功能,除非用户提出请求,否则绝不要实现此功能。要实现此功能:

  1. 在 AndroidManifest.xml 中声明 <uses-feature android:name="com.android.car.background_audio_while_driving" android:required="false" />,并同时声明 FOREGROUND_SERVICE 和 FOREGROUND_SERVICE_MEDIA_PLAYBACK 权限。
  2. 使用来自 androidx.car.app:app 的 CarFeatures.isFeatureEnabled(context, CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING) 检查车辆的运行时支持情况。仅在 !isBackgroundAudioWhileDrivingSupported && restrictions.isRequiresDistractionOptimization 时设置 shouldPreventPlay。
  3. 在导出的 MediaSessionService 中托管 MediaSession 和播放器,并设置 android:foregroundServiceType="mediaPlayback";在 UI 中使用 MediaController.Builder 和 buildAsync 连接。

第 5 步:保护浏览器应用中的敏感数据

由于车辆属于共享设备,浏览器必须在未通过个人资料锁保护的情况下,禁止访问已保存的密码和支付方式(SD-1)。在将密码或支付数据同步到车辆之前,浏览器必须提示用户进行身份验证,并在车载屏幕上显示通知,说明其数据将同步到车辆(SD-2)。

使用 KeyguardManager 检查是否设置了 PIN、图案或密码;如果未设置,则打开 Settings.ACTION_SECURITY_SETTINGS;并且仅使用 BiometricManager.Authenticators.DEVICE_CREDENTIAL 通过 BiometricPrompt 进行身份验证(车辆没有生物识别硬件):

val keyguardManager = context.getSystemService<KeyguardManager>()
val isDeviceSecure = keyguardManager?.isDeviceSecure == true
lateinit var biometricPrompt: BiometricPrompt

if (!isDeviceSecure) {
    context.startActivity(
        Intent(Settings.ACTION_SECURITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    )
} else {
    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle(context.getString(R.string.auth_title))
        .setSubtitle(context.getString(R.string.sync_data_to_car_notice))
        .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
        .build()
    biometricPrompt.authenticate(promptInfo)
}

第 6 步:验证实现

使用 ./gradlew assembleDebug 构建应用。查找并修复所有构建错误。

实现后的指导

实现后向用户提供以下指导:

  • 对于 Android Auto 应用,请将应用安装到手机上,并使用 Desktop Head Unit(DHU)进行测试。
  • 对于 Android Automotive OS,请将应用安装到 Android Studio 中的 Android Automotive OS 模拟器上。
  • 如果应用属于目标平台的 Beta 类别,则必须先获准加入早期访问计划,才能将应用提交到开放测试或正式发布轨道。
  • 如果应用实现了 Beta 驾驶时音频功能,则必须先获准加入早期访问计划,才能将应用提交到开放测试或正式发布轨道。
  • 提交应用前,请根据相关的车载应用质量指南检查应用。

反模式

  • 绝不在驻车应用的任何 <activity> 中包含 <meta-data android:name="distractionOptimized" android:value="true"/>,也绝不在视频应用中包含带有 uses name="video" 的 res/xml/automotive_app_desc.xml。
  • 绝不假设 Android Automotive OS 上存在硬件或软件返回按钮。
  • 绝不假设系统栏只位于顶部和底部,或始终可以隐藏。
  • 绝不在 Android Automotive OS 上使用 LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 或 LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES。
  • 绝不根据车速或挡位状态推断 UX 限制,而不读取 CarUxRestrictionsManager 中的 isRequiresDistractionOptimization。
  • 绝不在 Android Automotive OS 上的浏览器应用中将生物识别器(BIOMETRIC_STRONG、BIOMETRIC_WEAK)与 BiometricPrompt 搭配使用;只能使用 DEVICE_CREDENTIAL。
  • 绝不在用户只要求支持 Android Automotive OS 时添加 CAR_LAUNCHER intent 类别。
  • 绝不在用户只要求支持 Android Auto 时添加 android.hardware.type.automotive 功能标签。

最佳实践

  • 必须对不支持的硬件功能(wifi、screen.portrait、screen.landscape、camera)声明 android:required="false",并在调用 requestedOrientation 前检查 FEATURE_SCREEN_LANDSCAPE 和 FEATURE_SCREEN_PORTRAIT(DO-1)。
  • 必须在 Android Automotive OS 上(兼容模式除外)提供应用内返回和关闭操作入口(AN-1),并使用 WindowInsets.safeDrawing 和 OnControllableInsetsChangedListener 保护所有交互式 UI 和文本,避免被系统栏和显示挖孔遮挡(AR-1、AR-2)。
  • 必须在 UX 限制生效时暂停播放;除非支持并启用了驾驶时音频,否则在行驶期间从 MediaSession 中移除 COMMAND_PLAY_PAUSE(DD-2、DD-4、MC-1)。
  • 必须要求通过个人资料锁进行身份验证,并在保存、访问或同步浏览器密码或支付信息前显示屏幕同步披露说明(SD-1、SD-2)。

评论

支持 Markdown