1. 始める前に
この Codelab では、自動車向け Android アプリ ライブラリを使用して、Android Auto と Android Automotive OS 用の注意散漫防止の最適化済みアプリを作成する方法を学びます。まず、Android Auto のサポートを追加して、その後、Android Automotive OS 上で動作するアプリのバリエーションを、最小限の追加作業で作成します。両方のプラットフォームでアプリが動作したら、追加の画面や基本的なインタラクティビティを作成します。
必要なもの
- 最新の Android Studio。
- Kotlin の基本的な使用経験。
- Android サービスの基礎知識。
- Android 仮想デバイスを作成して Android Emulator で実行した経験。
- Android アプリのモジュール化の基礎知識。
- Builder デザイン パターンの基礎知識。
作成するアプリの概要
Android Auto | Android Automotive OS |
学習内容
- 自動車向けアプリ ライブラリのクライアント - ホスト アーキテクチャの仕組み。
- 独自の
CarAppService
クラス、Session
クラス、Screen
クラスの作成方法。 - Android Auto と Android Automotive OS の両方で実装を共有する方法。
- デスクトップ ヘッドユニットを使用して、開発マシン上で Android Auto を動作させる方法
- Android Automotive OS エミュレータの実行方法
2. 設定する
コードを取得する
- この Codelab のコードは、
car-codelabs
GitHub リポジトリ内のcar-app-library-fundamentals
ディレクトリにあります。クローンを作成するには、次のコマンドを実行します。
git clone https://github.com/android/car-codelabs.git
- または、リポジトリを ZIP ファイルとしてダウンロードすることもできます。
プロジェクトを開く
- Android Studio を起動し、
car-app-library-fundamentals/start
ディレクトリのみを選択してプロジェクトをインポートします。car-app-library-fundamentals/end
ディレクトリにはソリューション コードが含まれています。不明な点がある場合や、プロジェクト全体を確認したいときは、いつでも参照できます。
コードを理解する
- Android Studio でプロジェクトを開き、初期状態のコードを確認します。
アプリのスターター コードが、:app
と :common:data
の 2 つのモジュールに分かれていることに注目してください。
:app
モジュールには、モバイルアプリの UI とロジックが含まれています。:common:data
モジュールには、Place
モデルの読み取りに使用される、Place
モデルのデータクラスとリポジトリが含まれています。単純化のために、リポジトリはハードコードされたリストから読み取りますが、実際のアプリでは、データベースまたはバックエンド サーバーから簡単に読み取ることができます。
:app
モジュールには、:common:data
モジュールとの依存関係があるため、Place
モデルの一覧の読み取りや表示ができます。
3. 自動車向け Android アプリ ライブラリについて学ぶ
自動車向け Android アプリ ライブラリは、Jetpack ライブラリのセットです。これにより、開発者が自動車内で使用するアプリを構築できるようになります。このライブラリは、テンプレート化されたフレームワークによって、ドライバー向けに最適化されたユーザー インターフェースを提供します。また、車内で使われるさまざまなハードウェア構成(入力方法、画面サイズ、アスペクト比など)への適合にも対処します。これを利用することで、開発者は簡単にアプリを作成できるようになります。作成されたアプリは、さまざまな車両で、Android Auto と Android Automotive OS のどちらが動作していても、問題なく動作します。
仕組みを学ぶ
自動車向けアプリ ライブラリを使って作成されたアプリは、Android Auto や Android Automotive OS 上で直接動作させることはできません。代わりにホストアプリが、クライアント アプリ(開発者のアプリ)と通信し、クライアントのユーザー インターフェースを表示します。Android Auto はそれ自体がホストです。Google Automotive App Host は、Google 搭載の Android Automotive OS 車両で使用されるホストです。
自動車向けアプリ ライブラリの主なクラスのうち、アプリを作成する際に拡張する必要があるものは次のとおりです。
CarAppService
CarAppService
は、Android の Service
クラスのサブクラスで、ホストアプリがクライアント アプリ(この Codelab で作成するアプリなど)と通信するためのエントリー ポイントとして動作します。ホストアプリがやり取りする Session
インスタンスを作成するのが主な目的です。
セッション
Session
は、車両の画面上で動作する、クライアント アプリのインスタンスと考えることができます。他の Android コンポーネントと同じように、独自のライフサイクルがあり、Session
インスタンスが存在している間に使用されるリソースの初期化や破棄ができます。CarAppService
と Session
の間には、1 対多の関係があります。たとえば、1 つの CarAppService
が 2 つの Session
インスタンスをもつことがあります。1 つはプライマリ ディスプレイ用で、もう 1 つはクラスタ ディスプレイ用です。クラスタ ディスプレイはナビゲーション アプリ用で、クラスタ画面をサポートします。
画面
Screen
インスタンスは、ホストアプリが表示するユーザー インターフェースの生成を担います。ユーザー インターフェースは、グリッドやリストのような特定のレイアウト タイプをモデル化した Template
クラスで表されます。それぞれの Session
は、アプリのさまざまな部分でユーザーフローを処理する Screen
インスタンスのスタックを管理します。Session
と同様に、Screen
にも利用可能な独自のライフサイクルがあります。
これらのクラスの詳細については、この Codelab の CarAppService を作成するのセクションで CarAppService
、Session
、Screen
を作成する際に説明します。
4. 初期設定を行う
ます、CarAppService
を含むモジュールを設定して、依存関係を宣言します。
car-app-service モジュールを作成する
- プロジェクト ウィンドウで
:common
モジュールを選択し、右クリックして [New] > [Module] オプションを選択します。 - モジュール ウィザードが開かれるので、左側の一覧で [Android Library] テンプレートを選択します(これによって、このモジュールが依存関係として使えるようになります)。その後、次の値を設定します。
- Module name:
:common:car-app-service
- Package name:
com.example.places.carappservice
- Minimum SDK:
API 23: Android 6.0 (Marshmallow)
依存関係を設定する
libs.version.toml
ファイルに、androidx.car.app:app
アーティファクトのエントリを追加します。
libs.version.toml
[versions]
...
carApp = "1.7.0-rc01"
[libraries]
...
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
- 次に、
:common:car-app-service
モジュールのbuild.gradle.kts
ファイルに、2 つの依存関係を追加します。
androidx.car.app:app
。これは、自動車向けアプリ ライブラリのプライマリ アーティファクトで、アプリのビルド時に使用される、すべてのコアクラスが含まれています。ライブラリを構成するアーティファクトには、ほかに 3 つあります。androidx.car.app:app-projected
は Android Auto 特有の機能用、androidx.car.app:app-automotive
は、Android Automotive OS 機能のコード用、androidx.car.app:app-testing
は単体テストに役立つヘルパー用です。app-projected
とapp-automotive
を、この Codelab で後ほど使います。:common:data
。これは、既存のモバイルアプリで使われているのと同じデータ モジュールで、アプリ エクスペリエンスのすべてのバージョンで、同じデータソースが使えるようになります。
build.gradle.kts(:common:car-app-service モジュール)
dependencies {
...
implementation(libs.androidx.car.app)
implementation(project(":common:data"))
...
}
この変更によって、アプリに含まれるモジュールの依存関係グラフは次のようになります。
これで依存関係の設定は完了です。次は CarAppService
の作成に進みましょう。
5. CarAppService を作成する
- まず、
:common:car-app-service
モジュール内のcarappservice
パッケージに、PlacesCarAppService.kt
という名前のファイルを作成してください。 - このファイルの中に、
PlacesCarAppService
という名前のクラス(CarAppService
の拡張)を作成してください。
PlacesCarAppService.kt
import androidx.car.app.CarAppService
import androidx.car.app.Session
import androidx.car.app.SessionInfo
import androidx.car.app.validation.HostValidator
...
class PlacesCarAppService : CarAppService() {
override fun createHostValidator(): HostValidator {
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
}
override fun onCreateSession(sessionInfo: SessionInfo): Session {
// PlacesSession will be an unresolved reference until the next step
return PlacesSession()
}
}
抽象クラスの CarAppService
に、自分用の onBind
や onUnbind
のような Service
メソッドを実装します。これにより、メソッドがオーバーライドされるのを防ぎ、ホストアプリとの適切な相互運用性を確保します。やるべきことは、createHostValidator
と onCreateSession
の実装だけです。
createHostValidator
の戻り値である HostValidator
は、CarAppService
がバインドされるときに、信頼できるホストであることを確認するために参照されます。ホストが設定されたパラメータに一致しない場合は、バインドされません。この Codelab(および一般的なテスト)では、ALLOW_ALL_HOSTS_VALIDATOR
を使ってアプリの接続を簡単にしますが、製品版では使わないでください。製品版アプリでの設定方法の詳細については、createHostValidator
に関するドキュメントをご覧ください。
これくらいシンプルなアプリであれば、onCreateSession
は単に Session
のインスタンスを返すだけでも良いです。より複雑なアプリの場合は、車両でアプリが動作している間使用される統計情報やロギング クライアントのような、有効期間が長いリソースの初期化をここで行うのも良いでしょう。
- 最後に、
:common:car-app-service
モジュールのAndroidManifest.xml
ファイルに、PlacesCarAppService
に対応する<service>
エレメントを追加する必要があります。これにより、オペレーティング システム(およびホストのような他のアプリ)に存在を通知できるようになります。
AndroidManifest.xml(:common:car-app-service)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!--
This AndroidManifest.xml will contain all of the elements that should be shared across the
Android Auto and Automotive OS versions of the app, such as the CarAppService <service> element
-->
<application>
<service
android:name="com.example.places.carappservice.PlacesCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.POI" />
</intent-filter>
</service>
</application>
</manifest>
ここでは次の 2 つに注目します。
<action>
要素によって、ホスト(およびランチャー)アプリが、このアプリを見つけられるようになります。<category>
要素では、アプリのカテゴリ(この場合はスポット)を宣言します。これにより、満たすべきアプリの品質基準が決まります(詳細は後ほど説明します)。その他の有効な値については、サポートされているアプリのカテゴリをご覧ください。
PlacesSession クラスを作成する
PlacesCarAppService.kt
内に次のコードを追加します。
PlacesCarAppService.kt
import android.content.Intent
import androidx.car.app.Screen
...
class PlacesSession : Session() {
override fun onCreateScreen(intent: Intent): Screen {
// MainScreen will be an unresolved reference until the next step
return MainScreen(carContext)
}
}
このようなシンプルなアプリでは、onCreateScreen
でメイン画面を返すだけにもできます。しかし、このメソッドはパラメータとして Intent
を持つため、機能がより豊富なアプリであればそのパラメータを読み取るとることで、画面のバックスタックの追加や条件付きロジックの使用が可能になります。
MainScreen クラスを作成する
次に、screen.
という名前の新しいパッケージを作成します。
com.example.places.carappservice
パッケージを右クリックして、[New] > [Package] を選択します(パッケージのフルネームはcom.example.places.carappservice.screen
になります)。ここに、アプリのすべてのScreen
サブクラスを作成します。screen
パッケージに、MainScreen.kt
という名前のファイルを作成します。ここに、Screen
の拡張であるMainScreen
クラスが含まれます。これで、PaneTemplate
を使って、シンプルな Hello, world! というメッセージが表示されます。
MainScreen.kt
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.Pane
import androidx.car.app.model.PaneTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
...
class MainScreen(carContext: CarContext) : Screen(carContext) {
override fun onGetTemplate(): Template {
val row = Row.Builder()
.setTitle("Hello, world!")
.build()
val pane = Pane.Builder()
.addRow(row)
.build()
return PaneTemplate.Builder(pane)
.setHeader(
Header.Builder()
.setStartHeaderAction(Action.APP_ICON)
.build()
).build()
}
}
6. Android Auto のサポートを追加する
これで、アプリが起動して動作するのに必要なロジックを、すべて実装しました。しかし、アプリを Android Auto で動作させるには、あと 2 つ設定を行う必要があります。
car-app-service モジュールに依存関係を追加する
:app
モジュールの build.gradle.kts
ファイルに、次のような追加を行います。
build.gradle.kts(:app モジュール)
dependencies {
...
implementation(project(":common:car-app-service"))
...
}
この変更によって、アプリに含まれるモジュールの依存関係グラフは次のようになります。
これにより、:common:car-app-service
モジュールに作成したコードが、所定の権限付与アクティビティのような、自動車向けアプリ ライブラリに含まれる他のコンポーネントとバンドルされます。
com.google.android.gms.car.application メタデータを宣言する
:common:car-app-service
モジュールを右クリックして、[New] > [Android Resource File] オプションを選択し、次の値を上書きします。
- File name:
automotive_app_desc.xml
- Resource type:
XML
- Root element:
automotiveApp
- このファイルに次のような
<uses>
要素を追加し、アプリが、自動車向けアプリ ライブラリで提供されたテンプレートを使用することを宣言します。
automotive_app_desc.xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
<uses name="template"/>
</automotiveApp>
:app
モジュールのAndroidManifest.xml
ファイルに、作成したautomotive_app_desc.xml
ファイルを参照するための<meta-data>
エレメントを追加します。
AndroidManifest.xml(:app)
<application ...>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
...
</application>
Android Auto がこのファイルを読み取って、アプリがどのような機能を持っているか判断します。今回の場合は、アプリが自動車向けアプリ ライブラリのテンプレート システムを使用することがわかります。この情報はその後、アプリを Android Auto ランチャーに登録する、通知からアプリを開くなどの動作を処理するために使用されます。
省略可: 投影の変更をリッスンする
ユーザーのデバイスが自動車に接続されているかどうかを知りたいことがあると思います。接続状態をモニタリングできる LiveData
が含まれている CarConnection
API を使用することで、これを実現できます。
CarConnection
API を使うには、まず、androidx.car.app:app
アーティファクトの:app
モジュールに依存関係を追加します。
build.gradle.kts(:app モジュール)
dependencies {
...
implementation(libs.androidx.car.app)
...
}
- デモ用に、現在の接続状況を表示する、次のようなシンプルなコンポーザブルを作成できます。実際のアプリでは、ロギングでこの状態をキャプチャし、投影中にスマートフォンの画面で機能を無効にするなどに使用します。
MainActivity.kt
import androidx.car.app.connection.CarConnection
...
@Composable
fun ProjectionState(carConnectionType: Int, modifier: Modifier = Modifier) {
val text = when (carConnectionType) {
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not projecting"
CarConnection.CONNECTION_TYPE_NATIVE -> "Running on Android Automotive OS"
CarConnection.CONNECTION_TYPE_PROJECTION -> "Projecting"
else -> "Unknown connection type"
}
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
modifier = modifier
)
}
- これで、次のスニペットで示されているように、データの表示方法、読み取り方法、コンポーザブルに渡す方法がわかりました。
MainActivity.kt
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
...
setContent {
val carConnectionType by CarConnection(this).type.observeAsState(initial = -1)
PlacesTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column {
Text(
text = "Places",
style = MaterialTheme.typography.displayLarge,
modifier = Modifier.padding(8.dp)
)
ProjectionState(
carConnectionType = carConnectionType,
modifier = Modifier.padding(8.dp)
)
PlaceList(places = PlacesRepository().getPlaces())
}
}
}
}
- アプリを実行すると、Not projecting と表示されるはずです。
7. デスクトップ ヘッドユニット(DHU)でテストする
CarAppService
が実装され、Android Auto 設定も準備できたので、アプリを実行してどのように動作するか見てみましょう。
- スマートフォンにアプリをインストールし、指示に従って DHU のインストールと実行を行います。
DHU が起動して動作すると、ランチャーにアプリのアイコンが表示されます(表示されない場合、前のセクションですべてのステップを正しく実施したか再確認し、端末で DHU を終了してから再起動してください)。
アプリがクラッシュしました。
- アプリがクラッシュした理由を調べるには、Android Studio の Logcat を確認します(
package:mine
のデフォルトの Logcat フィルタを削除してis:error
に置き換える必要がある場合があります)。
Error: [type: null, cause: null, debug msg: java.lang.IllegalArgumentException: Min API level not declared in manifest (androidx.car.app.minCarApiLevel) at androidx.car.app.AppInfo.retrieveMinCarAppApiLevel(AppInfo.java:143) at androidx.car.app.AppInfo.create(AppInfo.java:91) at androidx.car.app.CarAppService.getAppInfo(CarAppService.java:380) at androidx.car.app.CarAppBinder.getAppInfo(CarAppBinder.java:255) at androidx.car.app.ICarApp$Stub.onTransact(ICarApp.java:182) at android.os.Binder.execTransactInternal(Binder.java:1285) at android.os.Binder.execTransact(Binder.java:1244) ]
ログを見ると、アプリがサポートする最小 API レベルの宣言がマニフェストにないことがわかります。追加する前に、なぜ必要なのか考えてみましょう。
Android 自体と同じように、自動車向けアプリ ライブラリにも API レベルのコンセプトがあります。ホストとクライアントのアプリが通信するためには、コントラクトが必要だからです。ホストアプリは指定された API レベルと関連する機能(およびより低いレベルに対する下位互換性)をサポートします。たとえば、SignInTemplate
は API レベル 2 以上で動作するホストで使用できます。しかし、API レベル 1 しかサポートしていないホストで使おうとすると、ホストはそのテンプレート タイプを知らないため、意味のあることは何もできません。
ホストをクライアントにバインドする処理では、サポートしている API レベルに共通部分がないと、正しくバインドできません。たとえば、ホストが API レベル 1 だけをサポートし、クライアント アプリは API レベル 2 の機能がないと動作しない場合(このマニフェスト宣言で示されているように)、このクライアント アプリはホスト上で正しく実行することはできないため、接続されるべきではありません。このため、必要な最小 API レベルは、クライアントがマニフェストで宣言しなければなりません。これにより、確実にそのレベルをサポートするホストだけをバインドできます。
- サポートする最小 API レベルを設定するには、
:common:car-app-service
モジュールのAndroidManfiest.xml
ファイルに、次のような<meta-data>
エレメントを追加します。
AndroidManifest.xml(:common:car-app-service)
<application>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1" />
<service android:name="com.example.places.carappservice.PlacesCarAppService" ...>
...
</service>
</application>
- アプリを再度インストールして、DHU で立ち上げます。今度は次のように表示されるはずです。
完全に理解するために、minCarApiLevel
を大きな値(たとえば 100)に設定して試してみるのも良いでしょう。ホストとクライアントに互換性がない場合にアプリを起動すると何が起こるか確認できます(ヒント: 値を設定していないときと同じようにクラッシュします)。
Android 自体と同じように、ホストが要求されたレベルをサポートしているかについて実行時に確認する場合、宣言された最小レベルよりも大きいレベルの API に含まれる機能も利用できます。
省略可: 投影の変更をリッスンする
- 前のステップで
CarConnection
リスナーを追加してあれば、DHU が動作しているとき、スマートフォン上で次のように状態が更新されます。
8. Android Automotive OS のサポートを追加する
Android Auto を起動して動作させることができました。次に、Android Automotive OS も同じようにサポートしましょう。
:automotive
モジュールを作成する
- アプリの Android Automotive OS 対応に必要なコードを含むモジュールを作成します。Android Studio で [File] > [New] > [New Module...] を開き、左側のテンプレート タイプ一覧から [Automotive] オプションを選択してください。その後、次の値を設定します。
- Application/Library name:
Places
(メインアプリと同じにしてありますが、別の名前にすることもできます) - Module name:
automotive
- Package name:
com.example.places.automotive
- 言語:
Kotlin
- Minimum SDK:
API 29: Android 10.0 (Q)
- 自動車向けアプリ ライブラリのアプリをサポートするすべての Android Automotive OS 車両は、API 29 以上で動作します。
- [Next] をクリックします。次の画面で [No Activity] を選択し、最後に [Finish] をクリックします。
依存関係を追加する
Android Auto のときと同じように、:common:car-app-service
モジュールで依存関係を宣言する必要があります。これによって、実装を両方のプラットフォームで共有できるようになります。
さらに、androidx.car.app:app-automotive
アーティファクトにも依存関係を追加します。androidx.car.app:app-projected
アーティファクトは、Android Auto ではオプションでしたが、Android Automotive OS では、アプリの実行に CarAppActivity
を使用するため必須です。
- まず、
libs.versions.toml
にandroidx.car.app:app-automotive
アーティファクトのエントリを追加します。
libs.version.toml
[libraries]
...
androidx-car-app-automotive = { group = "androidx.car.app", name = "app-automotive", version.ref = "carApp"}
- 依存関係を追加するには、
build.gradle.kts
ファイルを開き、次のコードを挿入します。
build.gradle.kts(:automotive モジュール)
dependencies {
...
implementation(project(":common:car-app-service"))
implementation(libs.androidx.car.app.automotive)
...
}
この変更によって、アプリに含まれるモジュールの依存関係グラフは次のようになります。
マニフェストをセットアップする
- まず、
android.hardware.type.automotive
とandroid.software.car.templates_host
の 2 つの機能が必須(required)であることを宣言します。
android.hardware.type.automotive
はシステム機能で、デバイス自体が車両であることを示します(詳細は FEATURE_AUTOMOTIVE
を参照)。この機能を必須としたアプリだけが、Google Play Console の Automotive OS トラックに送信できます(他のトラックに送信されるアプリは、この機能を必須にすることはできません)。android.software.car.templates_host
は、テンプレート アプリの実行に必要なテンプレート ホストを持つ車両にだけあるシステム機能です。 この Codelab では、これらの変更で十分です。独自のアプリを作成する場合は、アプリが Android Automotive OS の Google Play 機能の要件をすべて満たしていることを確認してください。
AndroidManifest.xml(:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<uses-feature
android:name="android.software.car.templates_host"
android:required="true" />
...
</manifest>
- 次に、Android Auto のときと同じように、
automotive_app_desc.xml
ファイルへの参照を追加する必要があります。
今回は、android:name
属性が以前と異なることに注意してください。com.google.android.gms.car.application
ではなく com.android.automotive
になります。以前と同じように、これで :common:car-app-service
モジュールの automotive_app_desc.xml
ファイルを参照するようになります。つまり、同じリソースが Android Auto と Android Automotive OS の両方で使われます。また、<meta-data>
要素は <application>
要素内にあります(そのため、application
タグが自己完結しているのを変更する必要があります)。
AndroidManifest.xml(:automotive)
<application>
...
<meta-data android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"/>
...
</application>
- 最後に、ライブラリに含まれている
CarAppActivity
用の<activity>
要素を追加する必要があります。
AndroidManifest.xml(:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<application ...>
...
<activity
android:name="androidx.car.app.activity.CarAppActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="distractionOptimized"
android:value="true" />
</activity>
</application>
</manifest>
これで、次のことが行われます。
app-automotive
パッケージに含まれるCarAppActivity
クラスの完全修飾されたクラス名が、android:name
に一覧表示されます。android:exported
がtrue
に設定されます。これは、このActivity
が、それ自体(ランチャー)以外のアプリから起動できる必要があるためです。android:launchMode
がsingleTask
に設定されます。これにより、同時に存在できるCarAppActivity
は 1 つだけになります。android:theme
が@android:style/Theme.DeviceDefault.NoActionBar
に設定されます。これにより、アプリは使用可能な全画面表示スペースをすべて使うことができます。- インテント フィルタは、これがアプリのランチャー
Activity
であることを示します。 - 車両の走行中など、UX 制限中にアプリを使用できることを OS に示す
<meta-data>
要素があります。
省略可: :app モジュールからランチャー アイコンをコピーする
:automotive
モジュールを作ったばかりなので、アイコンはデフォルトの緑の Android ロゴになっています。
- ロゴを変更したい場合、
:app
モジュールから:automotive
モジュールに、直接mipmap
リソースをコピーして貼り付けることで、モバイルアプリと同じランチャー アイコンを使うことができます。
9. Android Automotive OS エミュレータでテストする
Android Automotive OS Android Virtual Device を作成する
- デバイス マネージャーを開き、ウィンドウの左側にある [Category] 列で [Automotive] を選択します。次に、デバイス定義の一覧から [Automotive (1408p landscape) with Google Play] を選択し、[Next] をクリックします。
- 次のページで API 34 システム イメージを選択します(まだダウンロードされていない場合はダウンロードされます)。[Finish] をクリックして AVD を作成します。
アプリを実行する
前のステップで作成したエミュレータで、automotive
実行構成を使用してアプリを実行します。
次のステップでは、:common:car-app-service
モジュールに変更を加えて、場所の一覧を表示したり、ユーザーが他のアプリで選択した場所へのナビゲーションを開始したりできるようにします。
10. 地図と詳細画面を追加する
メイン画面に地図を追加する
- まず、
MainScreen
クラスに含まれているonGetTemplate
メソッドのコードを次のものに置き換えます。
MainScreen.kt
import androidx.car.app.model.CarLocation
import androidx.car.app.model.Distance
import androidx.car.app.model.DistanceSpan
import androidx.car.app.model.ItemList
import androidx.car.app.model.Metadata
import androidx.car.app.model.Place
import androidx.car.app.model.PlaceListMapTemplate
import androidx.car.app.model.PlaceMarker
import com.example.places.data.PlacesRepository
...
override fun onGetTemplate(): Template {
val placesRepository = PlacesRepository()
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage("No places to show")
placesRepository.getPlaces()
.forEach {
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
// Each item in the list *must* have a DistanceSpan applied to either the title
// or one of the its lines of text (to help drivers make decisions)
.addText(SpannableString(" ").apply {
setSpan(
DistanceSpan.create(
Distance.create(Math.random() * 100, Distance.UNIT_KILOMETERS)
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
)
})
.setOnClickListener { TODO() }
// Setting Metadata is optional, but is required to automatically show the
// item's location on the provided map
.setMetadata(
Metadata.Builder()
.setPlace(Place.Builder(CarLocation.create(it.latitude, it.longitude))
// Using the default PlaceMarker indicates that the host should
// decide how to style the pins it shows on the map/in the list
.setMarker(PlaceMarker.Builder().build())
.build())
.build()
).build()
)
}
return PlaceListMapTemplate.Builder()
.setTitle("Places")
.setItemList(itemListBuilder.build())
.build()
}
このコードは、PlacesRepository
から Place
インスタンスの一覧を読み取ります。また、それぞれのインスタンスを Row
に変換して ItemList
に追加し、PlaceListMapTemplate
で表示できるようにします。
- 再度アプリを(どちらか一方、または両方のプラットフォームで)実行して結果を見てみましょう。
Android Auto | Android Automotive OS |
別のエラーが発生しました – 権限が足りないようです。
java.lang.SecurityException: The car app does not have a required permission: androidx.car.app.MAP_TEMPLATES at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) at android.os.Parcel.createException(Parcel.java:2357) at android.os.Parcel.readException(Parcel.java:2340) at android.os.Parcel.readException(Parcel.java:2282) ...
- エラーを修正するために、
:common:car-app-service
モジュールのマニフェストに、次のような<uses-permission>
要素を追加します。
この権限は、PlaceListMapTemplate
を使用するすべてのアプリで宣言する必要があります。宣言がない場合、先ほどのようにアプリがクラッシュします。カテゴリの宣言で androidx.car.app.category.POI
を指定したアプリのみが、このテンプレートを使用でき、この権限も有効になることに注意してください。
AndroidManifest.xml(:common:car-app-service)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
...
</manifest>
権限を付与してからアプリを実行すると、それぞれのプラットフォームで次のように表示されます。
Android Auto | Android Automotive OS |
必要な Metadata
を提供すると、アプリのホストが地図の描画を行います。
詳細画面を追加する
次に、詳細画面を追加します。これによりユーザーは、特定の場所の詳細情報を見られるようになり、その場所へ好みのナビゲーション アプリでナビを開始するか、他の場所一覧に戻るかを選択できるようになります。これは、オプション操作ボタンの横に情報を 4 行まで表示できる PaneTemplate
を使用することで実現できます。
- まず、
:common:car-app-service
モジュールのres
ディレクトリを右クリックした後、[New] > [Vector Asset] をクリックし、次の設定を使ってナビゲーション アイコンを作成します。
- Asset type:
Clip art
- Clip art:
navigation
- 名前:
baseline_navigation_24
- Size:
24
dp by24
dp - 色:
#000000
- Opacity:
100%
- 続いて、
screen
パッケージで、DetailScreen.kt
という名前のファイルを(既存のMainScreen.kt
ファイルの横に)作成し、次のコードを追加します。
DetailScreen.kt
import android.graphics.Color
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Header
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Pane
import androidx.car.app.model.PaneTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.core.graphics.drawable.IconCompat
import com.example.android.cars.carappservice.R
import com.example.places.data.PlacesRepository
import com.example.places.data.model.toIntent
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
private var isBookmarked = false
override fun onGetTemplate(): Template {
val place = PlacesRepository().getPlace(placeId)
?: return MessageTemplate.Builder("Place not found")
.setHeader(
Header.Builder()
.setStartHeaderAction(Action.BACK)
.build()
)
.build()
val navigateAction = Action.Builder()
.setTitle("Navigate")
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.baseline_navigation_24
)
).build()
)
// Only certain Intent actions are supported by `startCarApp`. Check its documentation
// for all of the details. To open another app that can handle navigating to a location
// you must use the CarContext.ACTION_NAVIGATE action and not Intent.ACTION_VIEW like
// you might on a phone.
.setOnClickListener { carContext.startCarApp(place.toIntent(CarContext.ACTION_NAVIGATE)) }
.build()
return PaneTemplate.Builder(
Pane.Builder()
.addAction(navigateAction)
.addRow(
Row.Builder()
.setTitle("Coordinates")
.addText("${place.latitude}, ${place.longitude}")
.build()
).addRow(
Row.Builder()
.setTitle("Description")
.addText(place.description)
.build()
).build()
).setHeader(
Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(place.name)
.build()
).build()
}
}
navigateAction
の作成方法には特に注意が必要です。OnClickListener
での startCarApp
の呼び出しは、Android Auto や Android Automotive OS 上の他のアプリとの通信の鍵となります。
画面間を移動する
2 種類の画面が表示されましたので、その間の移動を追加しましょう。自動車向けアプリ ライブラリを使用したナビゲーションでは、プッシュとポップのスタックモデルを使用します。これは、運転中の作業に向いているシンプルなタスクフローに最適です。
MainScreen
上のリストアイテムの 1 つから、そのアイテムのDetailScreen
に移動するには、次のコードを追加します。
MainScreen.kt
Row.Builder()
...
.setOnClickListener { screenManager.push(DetailScreen(carContext, it.id)) }
...
DetailScreen
から MainScreen
へ戻る動作はすでに処理されています。これは、DetailScreen
に表示される PaneTemplate
が作成されるときに、setHeaderAction(Action.BACK)
が呼び出されるためです。ユーザーがヘッダー アクションをクリックすると、ホストがスタックから現在の画面をポップして取り除きます。ただし、この動作は必要に応じてアプリでオーバーライドできます。
- アプリを実行して
DetailScreen
とアプリ内移動が動作するのを確認しましょう。
11. 画面上のコンテンツを更新する
ユーザーの画面操作に応じて、画面上の要素の状態を変更したいことが、よくあると思います。この実現方法を示すために、DetailScreen
に表示された場所のブックマークを追加または削除する機能を作成します。
- まず、状態を保持するローカル変数
isBookmarked
を追加します。実際のアプリではデータレイヤーの一部として保存されるべきですが、デモ目的であれば、ローカル変数で十分です。
DetailScreen.kt
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
private var isBookmarked = false
...
}
- 次に、
:common:car-app-service
モジュールのres
ディレクトリを右クリックした後、[New] > [Vector Asset] をクリックし、次の設定を使用して 2 つのブックマーク アイコンを作成します。
- Asset type:
Clip art
- Name:
outline_bookmark_add_24
、outline_bookmark_added_24
- Clip art:
bookmark
、bookmark_added
(マテリアル シンボル アウトライン ソースセットから) - Size:
24
dp by24
dp - 色:
#000000
- Opacity:
100%
- 次に、
DetailsScreen.kt
で、ブックマーク機能のAction
を作成します。
DetailScreen.kt
val navigateAction = ...
val bookmarkAction = Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (isBookmarked) R.drawable.outline_bookmark_added_24 else R.drawable.outline_bookmark_add_24
)
).build()
)
.setOnClickListener {
isBookmarked = !isBookmarked
}.build()
...
ここでは、次の 2 つに注目してください。
CarIcon
は、アイテムの状態によって色が変わります。setOnClickListener
は、ユーザーの入力に応じてブックマークの状態を変更するために使用します。
bookmarkAction
を実際に使用するためには、PaneTemplate.Builder
でaddEndHeaderAction
を必ず呼び出してください。
DetailScreen.kt
Header.Builder()
...
.addEndHeaderAction(bookmarkAction)
.build()
- アプリを実行して動作を見てみましょう。
クリックは受信されているが、アイコンが変化しない。
これは、自動車向けアプリ ライブラリには、更新のコンセプトがあるためです。ドライバーの注意散漫を抑えるために、画面上のコンテンツ更新には一定の制限があります(表示されているテンプレートによって異なります)。また、コードで Screen
クラスの invalidate
メソッドを明示的に呼び出して要求したときにのみ更新されます。onGetTemplate
で参照されている状態を更新するだけでは、UI は更新されません。
- この問題を解決するには、
bookmarkAction
のOnClickListener
を次のように更新します。
DetailScreen.kt
.setOnClickListener {
isBookmarked = !isBookmarked
// Request that `onGetTemplate` be called again so that updates to the
// screen's state can be picked up
invalidate()
}
- アプリを再度実行して、クリックしたときにアイコンが更新されることを確認します。
これで、Android Auto と Android Automotive OS のどちらにも統合可能な基本アプリが作成できました。
12. 完了
初めての自動車向けアプリ ライブラリを使用したアプリが無事に作成できました。次は、学んだことを振り返り、自分のアプリに適用してみましょう。
前にも説明したとおり、現時点では、自動車向けアプリ ライブラリを使用して作成されたアプリのうち、特定のカテゴリのものだけを、Google Play ストアに送信できます。アプリがサポート対象のカテゴリのいずれかに該当する場合は、今すぐ作成を開始できます。
新しいアプリのカテゴリは毎年追加されています。そのため、学んだことを今すぐ生かすことができなくても、後でまた確認してください。そのときには、アプリが車で使えるようになっているかもしれません。
試してみたいこと
- OEM のエミュレータをインストールして、OEM のカスタマイズによって、Android Automotive OS の自動車向けアプリ ライブラリで作成したアプリのデザインが、どのように変わるか確認してみましょう。すべての OEM エミュレータが、自動車向けアプリ ライブラリのアプリをサポートしているわけではありませんのでご注意ください。
- さまざまな DHU 構成と Android Automotive OS エミュレータのハードウェア プロファイルを使用して、ホストアプリがさまざまなディスプレイサイズに合わせてアプリを適応させる方法を確認します。
- サンプルアプリを紹介するでは、自動車向けアプリ ライブラリのすべての機能を紹介していますのでご覧ください。
参考資料
- 自動車向け Android アプリ ライブラリを使用するでは、この Codelab のコンテンツのほかにも、多数のコンテンツが紹介されています。
- 自動車向け Android アプリ ライブラリの設計ガイドラインには、すべてのテンプレートの詳細な説明や、アプリ作成時に役立つベスト プラクティスが用意されています。
- 自動車向け Android アプリの品質に関するページでは、優れたユーザー エクスペリエンスを実現し、Google Play ストアの審査に合格するために、アプリが満たす必要がある基準について説明しています。