1. 始める前に
この Codelab では、自動車向け Android アプリ ライブラリを使用して、Android Auto と Android Automotive OS 用の注意散漫防止の最適化済みアプリを作成する方法を学びます。まず、Android Auto のサポートを追加して、その後、Android Automotive OS 上で動作するアプリのバリエーションを、最小限の追加作業で作成します。両方のプラットフォームでアプリが動作したら、追加の画面や基本的なインタラクティビティを作成します。
対象外:
- Android Auto と Android Automotive OS 用のメディア(オーディオ)アプリを作成する方法に関するガイド。これらのアプリの作成方法について詳しくは、自動車向けメディアアプリを作成するをご覧ください。
- Android Auto 用のメッセージ アプリを作成する方法に関するガイド。そのようなアプリの作成方法について詳しくは、Android Auto 用メッセージ アプリを作成するをご覧ください。
必要なもの
- Android Studio プレビュー版。Android Automotive OS エミュレータは、Android Studio プレビュー版でのみ利用できます。まだ Android Studio プレビュー版をインストールしていない場合、プレビュー版をダウンロードしながら、安定版リリースを使用して Codelab を開始できます。
- 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
Session
は、車両の画面上で動作する、クライアント アプリのインスタンスと考えることができます。他の Android コンポーネントと同じように、独自のライフサイクルがあり、Session
インスタンスが存在している間に使用されるリソースの初期化や破棄ができます。CarAppService
と Session
の間には、1 対多の関係があります。たとえば、1 つの CarAppService
が 2 つの Session
インスタンスをもつことがあります。1 つはプライマリ ディスプレイ用で、もう 1 つはクラスタ ディスプレイ用です。クラスタ ディスプレイはナビゲーション アプリ用で、クラスタ画面をサポートします。
Screen
Screen
インスタンスは、ホストアプリが表示するユーザー インターフェースの生成を担います。ユーザー インターフェースは、グリッドやリストのような特定のレイアウト タイプをモデル化した Template
クラスで表されます。それぞれの Session
は、アプリのさまざまな部分でユーザーフローを処理する Screen
インスタンスのスタックを管理します。Session
と同様に、Screen
にも利用可能な独自のライフサイクルがあります。
CarAppService
、Session
、Screen
は、この Codelab の CarAppService を作成するのセクションで作成しますので、今はあまり理解できなくても心配要りません。
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)
依存関係を設定する
- プロジェクト レベルの
build.gradle
ファイルに、自動車向けアプリ ライブラリのバージョンを表す、次の変数宣言を追加します。これにより、アプリ内のそれぞれのモジュールで、簡単に同じバージョンを使うことができます。
build.gradle(Project: Places)
buildscript {
ext {
// All versions can be found at https://developer.android.com/jetpack/androidx/releases/car-app
car_app_library_version = '1.3.0-rc01'
...
}
}
- 次に、
:common:car-app-service
モジュールのbuild.gradle
ファイルに、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(:common:car-app-service モジュール)
dependencies {
...
implementation "androidx.car.app:app:$car_app_library_version"
implementation project(":common:data")
...
}
この変更によって、アプリに含まれるモジュールの依存関係グラフは次のようになります。
これで依存関係の設定は完了です。次は CarAppService
の作成に進みましょう。
5. CarAppService を作成する
- まず、
:common:car-app-service
モジュール内のcarappservice
パッケージに、PlacesCarAppService.kt
という名前のファイルを作成してください。 - このファイルの中に、
PlacesCarAppService
という名前のクラス(CarAppService
の拡張)を作成してください。
PlacesCarAppService.kt
class PlacesCarAppService : CarAppService() {
override fun createHostValidator(): HostValidator {
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
}
override fun onCreateSession(): 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>
要素では、アプリのカテゴリを宣言します。これにより、満たすべきアプリの品質基準が決まります(詳細は後ほど説明します)。他に、androidx.car.app.category.NAVIGATION
やandroidx.car.app.category.IOT
の値になることがあります。
PlacesSession クラスを作成する
- 新しく
PlacesCarAppService.kt
ファイルを作成し、次のコードを追加します。
PlacesCarAppService.kt
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
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)
.setHeaderAction(Action.APP_ICON)
.build()
}
}
6. Android Auto のサポートを追加する
これで、アプリが起動して動作するのに必要なロジックを、すべて実装しました。しかし、アプリを Android Auto で動作させるには、あと 2 つ設定を行う必要があります。
car-app-service モジュールに依存関係を追加する
:app
モジュールの build.gradle
ファイルに、次のような追加を行います。
build.gradle(:app モジュール)
dependencies {
...
implementation project(path: ':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(:app モジュール)
dependencies {
...
implementation "androidx.car.app:app:$car_app_library_version"
...
}
- デモ用に、現在の接続状況を表示する、次のようなシンプルなコンポーザブルを作成できます。実際のアプリでは、ロギングでこの状態をキャプチャし、投影中にスマートフォンの画面で機能を無効にするなどに使用します。
MainActivity.kt
@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
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 を終了してから再起動してください)。
アプリがクラッシュしました。
- アプリがクラッシュした理由を調べるには、右上の角にあるデバッグ アイコン(DHU で動作しているときのみ表示されます)を切り替えるか、Android Studio の Logcat を確認します。
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
- Language:
Kotlin
- Minimum SDK:
API 29: Android 10.0 (Q)
-:common:car-app-service
モジュール作成時に説明したとおり、自動車向けアプリ ライブラリのアプリをサポートするすべての 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
を使用するため必須です。
- 依存関係を追加するには、
build.gradle
ファイルを開き、次のコードを挿入します。
build.gradle(:automotive モジュール)
dependencies {
...
implementation project(':common:car-app-service')
implementation "androidx.car.app:app-automotive:$car_app_library_version"
...
}
この変更によって、アプリに含まれるモジュールの依存関係グラフは次のようになります。
マニフェストをセットアップする
- まず、
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
は、テンプレート アプリの実行に必要なテンプレート ホストを持つ車両にだけあるシステム機能です。
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>
- 次に、必須ではない機能をいくつか宣言する必要があります。
これは、アプリを Google 搭載車両の幅広いハードウェアに適合させるためです。たとえば、アプリが android.hardware.screen.portrait
機能を必須とする場合、ほとんどの車両で画面の向きは固定なので、横表示画面の車両には適合しません。そのため、この機能の android:required
属性を false
に設定します。
AndroidManifest.xml(:automotive)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
...
</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 の両方で使われます。<application>
要素内の <meta-data>
要素に注意してください(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 エミュレータでテストする
Automotive with Play Store のシステム イメージをインストールする
- まず、Android Studio で SDK Manager を開き、まだ選択していない場合は [SDK Platforms] タブを選択します。SDK Manager ウィンドウの右下にある [Show package details] チェックボックスがオンになっていることを確認します。
- 次のエミュレータ イメージのうち 1 つ以上をインストールします。イメージは、同じアーキテクチャ(x86 / ARM)のマシンでのみ実行できます。
- Android 12L > Automotive with Play Store Intel x86 Atom_64 System Image
- Android 12L > Automotive with Play Store ARM 64 v8a System Image
- Android 11 > Automotive with Play Store Intel x86 Atom_64 System Image
- Android 10 > Automotive with Play Store Intel x86 Atom_64 System Image
Android Automotive OS の Android Virtual Device を作成する
- デバイス マネージャーを開き、ウィンドウの左側にある [Category] 列で [Automotive] を選択します。次に、デバイス定義の一覧から [Automotive (1024p landscape)] を選択し、[Next] をクリックします。
- 次のページに移動したら、前のステップでインストールしたシステム イメージを選択します(Android 11/API 30 のイメージを選択する場合、デフォルトの Recommended タブではなく、x86 Images タブにあるかもしれません)。[Next] をクリックし、必要に応じて詳細オプションを選択したら、[Finish] をクリックして AVD を作成します。
アプリを実行する
- 前のステップで作成したエミュレータで、
automotive
実行構成を使用してアプリを実行します。
最初にアプリを実行すると、次のような画面が表示されるかもしれません。
その場合は、[Check for updates] ボタンをクリックすると、Google Play ストアのページに移動します。そこで、[Install] ボタンをクリックし、Google Automotive App Host アプリをインストールします。[Check for updates] ボタンをクリックしたときにログインしていなかった場合、ログインフローに移行します。ログイン後、アプリを再度開いてボタンをクリックすると、Google Play ストアのページに戻ります。
- 最後に、インストールされたホストで、ランチャー(下部にある 9 つのドットのグリッド アイコン)から再度アプリを開くと、次のように表示されます。
次のステップでは、:common:car-app-service
モジュールに変更を加えて、場所の一覧を表示したり、ユーザーが他のアプリで選択した場所へのナビゲーションを開始したりできるようにします。
10. 地図と詳細画面を追加する
メイン画面に地図を追加する
- まず、
MainScreen
クラスに含まれているonGetTemplate
メソッドのコードを次のものに置き換えます。
MainScreen.kt
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
- Name:
baseline_navigation_24
- Size:
24
dp by24
dp - Color:
#000000
- Opacity:
100%
- 続いて、
screen
パッケージで、DetailScreen.kt
という名前のファイルを(既存のMainScreen.kt
ファイルの横に)作成し、次のコードを追加します。
DetailScreen.kt
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
override fun onGetTemplate(): Template {
val place = PlacesRepository().getPlace(placeId)
?: return MessageTemplate.Builder("Place not found")
.setHeaderAction(Action.BACK)
.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()
)
.setTitle(place.name)
.setHeaderAction(Action.BACK)
.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
に表示された場所を、お気に入りに追加したり、お気に入りから削除したりする機能を作成します。
- まず、状態を保持するローカル変数
isFavorite
を追加します。実際のアプリではデータレイヤーの一部として保存されるべきですが、デモ目的であれば、ローカル変数で十分です。
DetailScreen.kt
class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
private var isFavorite = false
...
}
- 次に、
:common:car-app-service
モジュールのres
ディレクトリを右クリックした後、[New] > [Vector Asset] をクリックし、次の設定を使用してお気に入りアイコンを作成します。
- Asset type:
Clip art
- Name:
baseline_favorite_24
- Clip art:
favorite
- Size:
24
dp by24
dp - Color:
#000000
- Opacity:
100%
- その後
DetailsScreen.kt
で、PaneTemplate
用のActionStrip
を作成します。
ActionStrip
UI コンポーネントはヘッダー行のタイトルと反対側にあり、2 番目、3 番目の操作に適しています。ナビゲーションが DetailScreen
上でのメイン操作なので、お気に入りに追加、お気に入りから削除の Action
を ActionStrip
に置くのは、とても良い画面構成です。
DetailScreen.kt
val navigateAction = ...
val actionStrip = ActionStrip.Builder()
.addAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.baseline_favorite_24
)
).setTint(
if (isFavorite) CarColor.RED else CarColor.createCustom(
Color.LTGRAY,
Color.DKGRAY
)
).build()
)
.setOnClickListener {
isFavorite = !isFavorite
}.build()
)
.build()
...
ここでは、次の 2 つに注目してください。
CarIcon
は、アイテムの状態によって色が変わります。setOnClickListener
は、ユーザーの入力に応じてお気に入りの状態を変更するために使用します。
- 使用するためには、
PaneTemplate.Builder
でsetActionStrip
を必ず呼び出してください。
DetailScreen.kt
return PaneTemplate.Builder(...)
...
.setActionStrip(actionStrip)
.build()
- アプリを実行して動作を見てみましょう。
クリックはされているようですが、UI が更新されません。
これは、自動車向けアプリ ライブラリには、更新のコンセプトがあるためです。ドライバーの注意散漫を抑えるために、画面上のコンテンツ更新には一定の制限があります(表示されているテンプレートによって異なります)。また、コードで Screen
クラスの invalidate
メソッドを明示的に呼び出して要求したときにのみ更新されます。onGetTemplate
で参照されている状態を更新するだけでは、UI は更新されません。
- この問題を修正するには、
OnClickListener
を次のように変更します。
DetailScreen.kt
.setOnClickListener {
isFavorite = !isFavorite
// 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 ストアに送信できます。作成したアプリがナビゲーション アプリ、(この Codelab で作成したような)スポット(POI)アプリ、モノのインターネット(IOT)アプリであれば、すぐに開発を始めて、両方のプラットフォームで製品版をリリースできます。
新しいアプリのカテゴリは毎年追加されています。そのため、学んだことを今すぐ生かすことができなくても、後でまた確認してください。そのときには、アプリが車で使えるようになっているかもしれません。
試してみたいこと
- OEM のエミュレータ(Polestar 2 エミュレータなど)をインストールして、OEM のカスタマイズによって、Android Automotive OS の自動車向けアプリ ライブラリで作成したアプリのデザインが、どのように変わるか確認してみましょう。すべての OEM エミュレータが、自動車向けアプリ ライブラリのアプリをサポートしているわけではありませんのでご注意ください。
- サンプルアプリを紹介するでは、自動車向けアプリ ライブラリのすべての機能を紹介していますのでご覧ください。
参考資料
- 自動車向け Android アプリ ライブラリを使用するでは、この Codelab のコンテンツのほかにも、多数のコンテンツが紹介されています。
- 自動車向け Android アプリ ライブラリの設計ガイドラインには、すべてのテンプレートの詳細な説明や、アプリ作成時に役立つベスト プラクティスが用意されています。
- 自動車向け Android アプリの品質に関するページでは、優れたユーザー エクスペリエンスを実現し、Google Play ストアの審査に合格するために、アプリが満たす必要がある基準について説明しています。