既存のAndroidアプリを車載パーキングアプリに適応させる
既存のAndroidビデオアプリ、ゲーム、ウェブブラウザを、Android AutoおよびAndroid Automotive OS上でパーキングアプリとして実行できるように適応させます。以下の手順に従って、Google Play配布のための車載アプリ品質ガイドラインを満たします。
前提条件
- サポートされているパーキングアプリカテゴリ(ビデオストリーミングアプリ、ゲーム、ウェブブラウザ)のいずれかに該当する、既存の適応型Androidアプリを用意してください。
用語集
- パーキングアプリ: パーキングアプリは、車両が駐車中にのみAndroid AutoおよびAndroid Automotive OS上で実行されます。3つのパーキングカテゴリはビデオ、ゲーム、ブラウザです。他のカテゴリはパーキングアプリとして公開できません。
- 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"を設定する必要があり、app/src/main/res/xml/automotive_app_desc.xmlにuses name="video"を含めてはなりません。 - ゲーム: ゲームは
<application>にandroid:appCategory="game"を設定する必要があります。アプリがコントローラー入力をサポートする場合は、android:required="false"を指定してandroid.hardware.gamepadを宣言します。 - ブラウザ: ブラウザは、エクスポートされた
<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"、専用の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に対して設定し、遠くのディスプレイに移動したり戻ったりしてもアクティビティが再作成されたり、再生が途切れたりしないようにします。 - 互換モードと注意散漫: 互換モードの宣言はオプションであり、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>
インタラクティブなUI、アプリバー、スクロール可能なリストをWindowInsets.safeDrawingで保護します。
ステップ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になったら再生を一時停止し、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: 運転中のバックグラウンドオーディオをサポートする(オプションのベータ機能)
これはオプションのベータ機能であるため、ユーザーから要求がない限り、この機能を決して実装しないでください。機能を実装するには:
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内にホストし、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を使用してアプリをビルドします。ビルドエラーを見つけて修正します。
実装後の確認
…
[翻訳のため省略]