Communitygithub.com

android/automotive-adaptation-kr

기존 Android 비디오 스트리밍 앱, 게임 및 웹 브라우저를 Android Auto 및 Android Automotive OS에서 주차(Parked) 앱으로 실행되도록 변환합니다. 매니페스트 구성, Google Play 하드웨어 기능 선언, 호환성 모드, 뒤로 가기 탐색, 방향 및 디스플레이 연속성, 제어 가능한 시스템 바 삽입, 불규칙 디스플레이 컷아웃, 운전자 주의 분산 생명 주기 처리, CarUxRestrictionsManager를 이용한 MediaSession 재생 제한, 운전 중 백그라운드 오디오 및 브라우저 민감 데이터 보호를 다룹니다. 주차 앱을 자동차용 앱 품질 가이드라인에 맞게 마이그레이션, 빌드 또는 감사할 때 이 스킬을 사용하세요. 운전 중 사용하도록 설계된 미디어 오디오 앱, 템플릿 기반 Car App Library 내비게이션, 관심 장소, 사물 인터넷, 날씨 또는 통신 앱에는 사용하지 마십시오.

automotive-adaptation-kr란 무엇인가요?

이 스킬은 기존 Android 비디오 스트리밍 앱, 게임 및 웹 브라우저를 Android Auto와 Android Automotive OS(AAOS)에서 차량이 주차된 상태에서만 실행되는 '주차(Parked) 앱'으로 변환하는 방법을 상세히 제공합니다. Google Play 배포를 위한 자동차 앱 품질 가이드라인을 준수하도록 AndroidManifest.xml 설정, 하드웨어 기능 요구 사항 선언, 디스플레이 연속성 처리, 호환성 모드 적용, 뒤로 가기 및 닫기 UI 제공, 방향 고정 요청 방지, 시스템 바 삽입 제어, 불규칙 디스플레이 컷아웃 안전 렌더링 등의 단계를 안내합니다. 또한 운전자 주의 분산 방지를 위해 생명 주기 전환 시 재생 중지, CarUxRestrictionsManager를 활용한 MediaSession 재생 제한, 브라우저 앱의 민감한 데이터(저장된 비밀번호, 결제 정보) 보호를 위한 지문 또는 기기 자격 증명 인증 방법을 설명합니다. 선택적 베타 기능인 '운전 중 백그라운드 오디오' 구현도 포함되어 있으며, Android Auto(Android 15+)와 Android Automotive OS 모두를 지원합니다. 이 스킬은 기존 적응형 Android 앱을 보유한 개발자가 주차 앱 카테고리(비디오, 게임, 브라우저)에 해당하는 앱을 자동차 플랫폼에 맞게 최적화하고 출시할 때 필수적인 가이드 역할을 합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/android/skills/tree/HEAD/cars/android-for-cars-parked-apps

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

기존 Android 비디오 앱, 게임 및 웹 브라우저를 자동차 주차 앱으로 변환하기

기존 Android 비디오 앱, 게임 및 웹 브라우저를 Android Auto 및 Android Automotive OS에서 주차(Parked) 앱으로 실행되도록 변환합니다. 다음 단계를 따라 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에서 베타로 제공되며, Android Auto에서는 지원되지 않습니다.
  • 미디어 카테고리: 주차 카테고리가 아닙니다. 미디어 앱은 운전 중 또는 주차 중에 오디오 콘텐츠(음악, 라디오, 오디오북, 비디오 제외)를 재생합니다.
  • Android for Cars 플랫폼: Android for Cars는 Android Auto와 Android Automotive OS로 구성된 포괄적인 용어입니다.
  • 일반 사용 가능 카테고리: 지원되는 플랫폼의 내부 테스트, 비공개 테스트, 공개 테스트 및 프로덕션 Google Play 트랙에서 허용됩니다.
  • 베타 카테고리: 내부 또는 비공개 테스트 트랙에서 허용되며, 공개 테스트 및 프로덕션 트랙은 얼리 액세스 프로그램에 승인되어야 합니다.
  • 운전 중 오디오: Android Automotive OS의 베타 기능으로, 비디오 앱이 차량이 움직이는 동안 오디오를 재생할 수 있도록 합니다.
  • 자동차 앱 품질 가이드라인: Tier 3(Car ready), Tier 2(Car optimized) 또는 Tier 1(Car differentiated) 기준에 대해 앱을 감사할 때 자동차 앱 품질 가이드라인을 반드시 확인하세요.
  • 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에서 앱 카테고리, 대상 플랫폼, 하드웨어 기능 요구 사항 및 활동 구성을 선언합니다.

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"/>를 반드시 추가해야 합니다. 사용자가 다른 인텐트 필터를 지정하지 않는 한 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"는 모바일 또는 다중 폼 팩터 트랙용, "true"는 앱이 전용 Automotive OS 트랙에 게시되는 경우에만). 앱이 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를 구성하여 멀리 떨어진 디스플레이로 이동하거나 디스플레이에서 올 때 활동이 다시 생성되거나 재생이 끊기는 것을 방지합니다.
  • 호환성 모드 및 주의 분산: 호환성 모드 선언은 선택 사항이며 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를 반환하면 중복된 인앱 뒤로 가기 또는 닫기 UI를 숨겨야 합니다. 주차 앱은 <activity>에 <meta-data android:name="distractionOptimized" android:value="true"/>를 절대 포함해서는 안 됩니다(DD-2, DD-3).

2단계: 뒤로 가기 탐색 및 방향 지원 제공

2.1: 인앱 뒤로 가기 및 닫기 UI 추가

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). 하드코딩된 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)를 사용하지 마십시오. res/values-car-v30/integers.xml에서 car 리소스 한정자를 사용하여 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: 생명 주기 전환 시 재생 일시 중지 및 운전 재개 차단

차량이 움직이기 시작하면 OS는 주차된 활동을 차단 오버레이로 덮고 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가 되면 재생을 일시 중지하고, 외부 MediaSession 컨트롤이 운전 중에 재생을 재개할 수 없도록 getState에서 COMMAND_PLAY_PAUSE를 제거합니다.
@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: 운전 중 백그라운드 오디오 지원 (선택적 베타 기능)

이는 선택적 베타 기능이므로, 사용자가 요청하지 않는 한 절대 이 기능을 구현하지 마십시오. 기능을 구현하려면:

  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과 플레이어를 android:foregroundServiceType="mediaPlayback"이 있는 내보낸 MediaSessionService에 호스팅하고, UI에서 MediaController.Builder 및 buildAsync를 사용하여 연결합니다.

5단계: 브라우저 앱에서 민감한 데이터 보호

차량은 공유 장치이므로 브라우저는 프로필 잠금으로 보호되지 않는 한 저장된 비밀번호 및 결제 방법에 대한 액세스를 반드시 차단해야 합니다(SD-1). 비밀번호 또는 결제 데이터를 차량에 동기화하기 전에 브라우저는 반드시 사용자에게 인증을 요청하고 데이터가 차량에 동기화될 것이라는 알림을 차량 화면에 표시해야 합니다(SD-2).

KeyguardManager를 사용하여 PIN, 패턴 또는 비밀번호가 설정되었는지 확인하고, 설정되지 않은 경우 Settings.ACTION_SECURITY_SETTINGS를 열고, BiometricPrompt를 사용하여 BiometricManager.Authenticators.DEVICE_CREDENTIAL만으로 인증합니다(차량에는 생체 인식 하드웨어가 없습니다).

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를 사용하여 앱을 빌드합니다. 빌드 오류를 찾아 수정하세요.

구현 후

…

[번역을 위해 잘림]

관련 스킬