기존 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을 충족하려면 다음을 따릅니다.
androidx.lifecycle:lifecycle-runtime-compose의LifecycleEventEffect를 사용하여ON_PAUSE또는ON_STOP에서 재생을 일시 중지합니다.- 재생, 일시 중지 또는 중지 명령과 항목 메타데이터(
MC-1)가 포함된MediaSession(androidx.media3:media3-session)을 노출하고onCleared에서 해제합니다. 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: 운전 중 백그라운드 오디오 지원 (선택적 베타 기능)
이는 선택적 베타 기능이므로, 사용자가 요청하지 않는 한 절대 이 기능을 구현하지 마십시오. 기능을 구현하려면:
AndroidManifest.xml에<uses-feature android:name="com.android.car.background_audio_while_driving" android:required="false" />를FOREGROUND_SERVICE및FOREGROUND_SERVICE_MEDIA_PLAYBACK권한과 함께 선언합니다.androidx.car.app:app에서CarFeatures.isFeatureEnabled(context, CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING)로 런타임 차량 지원을 확인합니다.!isBackgroundAudioWhileDrivingSupported && restrictions.isRequiresDistractionOptimization인 경우에만shouldPreventPlay를 설정합니다.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를 사용하여 앱을 빌드합니다. 빌드 오류를 찾아 수정하세요.
구현 후
…
[번역을 위해 잘림]