Communitygithub.com

android/skills

將現有的 Android 影片串流應用程式、遊戲和網頁瀏覽器調整為可在 Android Auto 和 Android Automotive OS 上以停車模式運作。涵蓋 Manifest 配置、Google Play 硬體功能宣告、相容模式、返回導航支援、螢幕方向與顯示連續性、可控制的系統列內邊距、不規則顯示器開孔處理、駕駛分心生命週期管理、使用 CarUxRestrictionsManager 的 MediaSession 播放限制、駕駛中的背景音訊以及瀏覽器敏感資料保護。在遷移、建置或稽核停車模式應用程式以符合 Car App Quality 指南時使用此技能。不適用於媒體音訊應用程式、基於模板的 Car App Library 導航、興趣點、物聯網、天氣或在駕駛中使用的通訊應用程式。

skills 是什麼?

此技能專門用於指導開發人員將現有的 Android 應用程式(影片串流、遊戲和網頁瀏覽器)轉換為 Android Auto 和 Android Automotive OS (AAOS) 上專用的「停車模式應用程式」。它提供逐步指引,以確保應用程式符合 Google Play 發布所需的 Car App Quality 指南。技能涵蓋了從 AndroidManifest.xml 配置(包括宣告應用程式類別、目標平台和硬體功能)到實作返回導航、處理系統列內邊距和不規則顯示器開孔,以及滿足駕駛分心要求的完整工作流程。此外,還包含實作 MediaSession 播放限制(使用 CarUxRestrictionsManager)以及可選的駕駛中背景音訊功能,並針對瀏覽器類別提供保護敏感資料(如密碼和付款方式)的具體實作方法。此技能對於將汽車應用程式從行動裝置遷移過來、從頭開始建置合規的車用應用程式,或稽核現有應用程式的開發人員來說非常有價值。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/android/skills/tree/HEAD/cars/android-for-cars-parked-apps

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

將現有的 Android 影片應用程式、遊戲和網頁瀏覽器調整為可在 Android Auto 和 Android Automotive OS 上以停車模式運作。請遵循以下步驟,以符合 Google Play 發布的 Car App Quality 指南。

先決條件

  • 從一個現有的、支援的停車模式類別(影片串流應用程式、遊戲或網頁瀏覽器)的 Adaptive Android App 開始。

詞彙表

  • Parked apps(停車模式應用程式):停車模式應用程式僅在車輛停車時於 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 不同的平台。
  • Video category(影片類別):影片應用程式在停車時顯示串流影片。通常在 Android Automotive OS 上可用;Android Auto 上不支援。
  • Games category(遊戲類別):遊戲應用程式在停車時提供遊戲體驗。通常在 Android Automotive OS 和 Android Auto 上均可使用。
  • Browsers category(瀏覽器類別):網頁瀏覽器讓使用者在停車時瀏覽網頁。在 Android Automotive OS 上以 Beta 版提供;Android Auto 上不支援。
  • Media category(媒體類別):非停車模式類別。媒體應用程式在駕駛或停車時播放音訊內容(音樂、廣播、有聲書,不包括影片)。
  • Android for Cars platform:Android for Cars 是一個統稱,包含 Android Auto 和 Android Automotive OS。
  • Generally available categories(全面上線類別):允許在支援平台上使用內部測試、封閉測試、公開測試和正式發布的 Google Play 軌道。
  • Beta categories(Beta 類別):僅允許在內部或封閉測試軌道上使用;公開測試和正式發布軌道則需要先加入搶先體驗計畫。
  • Audio while driving(駕駛中音訊):Android Automotive OS 上的 Beta 功能,讓影片應用程式在車輛行駛時可以播放音訊。
  • Car app quality guidelines:在依據 Tier 3 Car ready、Tier 2 Car optimized 或 Tier 1 Car differentiated 標準稽核應用程式時,必須檢查 car app quality guidelines。
  • Android Automotive OS compatibility mode:相容模式 (android.software.car.display_compatibility) 是特定車款上的系統功能,提供系統返回操作、安全區域渲染、密度縮放以及不透明的駕駛模式阻擋(將 Activity 移至 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:配置應用程式 Manifest

在 AndroidManifest.xml 中宣告應用程式類別、目標平台、硬體功能需求和 Activity 配置。

1.1:識別應用程式類別

  • Video apps(影片應用程式):影片應用程式必須在 <application> 上設定 android:appCategory="video",並且不得包含 app/src/main/res/xml/automotive_app_desc.xml(其中包含 uses name="video")。
  • Games(遊戲):遊戲必須在 <application> 上設定 android:appCategory="game"。如果應用程式支援控制器輸入,請以 android:required="false" 宣告 android.hardware.gamepad。
  • Browsers(瀏覽器):瀏覽器必須在已匯出 <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> 上配置 android:configChanges 為 screenSize、smallestScreenSize、orientation、screenLayout 和 density,以便在移動到遠端顯示器或從遠端顯示器移回時,不會重新建立 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:value="true" 宣告 android.software.car.display_compatibility:僅在 PackageManager.hasSystemFeature("android.software.car.display_compatibility") 回傳 false 時,才顯示應用程式內返回與關閉控制項。

2.2:保護執行階段的螢幕方向請求

車輛顯示器具有固定的方向。在固定為直向的車用顯示器上請求橫向會導致黑邊或閃爍迴圈 (DO-1)。移除在 Manifest 中寫死的 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. 暴露一個 MediaSession (androidx.media3:media3-session),包含播放、暫停或停止指令以及媒體項目的中繼資料 (MC-1),並在 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. 將 MediaSession 和播放器託管在一個已匯出的 MediaSessionService 中,該 Service 需設定 android:foregroundServiceType="mediaPlayback",並使用 MediaController.Builder 和 buildAsync 從 UI 連接。

步驟 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 建置應用程式。找出並修正任何建置錯誤。

實作後

…

[內容因翻譯而截斷]

相關技能