このページでは、ターンバイターン方式のナビゲーション アプリの機能を実装するために使用できる、自動車向けアプリ ライブラリのさまざまな機能について詳しく説明します。
マニフェストでナビゲーション サポートを宣言する
ナビゲーション アプリでは、CarAppService
のインテント フィルタで自動車アプリのカテゴリとして androidx.car.app.category.NAVIGATION
を宣言する必要があります。
<application>
...
<service
...
android:name=".MyNavigationCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.NAVIGATION"/>
</intent-filter>
</service>
...
</application>
ナビゲーション インテントをサポートする
アプリに送信されるナビゲーション インテント(音声クエリを使用して Google アシスタントから取得したものを含む)をサポートするためには、アプリは Session.onCreateScreen
と Session.onNewIntent
で CarContext.ACTION_NAVIGATE
インテントを処理する必要があります。
インテントの形式について詳しくは、CarContext.startCarApp
のドキュメントをご覧ください。
ナビゲーション テンプレートにアクセスする
ナビゲーション アプリは、以下のテンプレートにアクセスできます。これらのテンプレートは、背景のサーフェスに地図を表示し、ナビゲーション使用時にはターンバイターン方式のルート案内を表示します。
NavigationTemplate
: ナビゲーション使用時に任意の情報メッセージと推定所要時間も表示します。MapWithContentTemplate
: アプリがなんらかのコンテンツ(リストなど)を含む地図タイルのレンダリングを可能にするテンプレート。通常、コンテンツは地図タイルの上にオーバーレイとしてレンダリングされ、地図は表示されたまま、安定した領域がコンテンツに合わせて調整されます。
これらのテンプレートを使用してナビゲーション アプリのユーザー インターフェースを設計する方法について詳しくは、ナビゲーション アプリをご覧ください。
ナビゲーション テンプレートにアクセスするには、AndroidManifest.xml
ファイルで androidx.car.app.NAVIGATION_TEMPLATES
権限を宣言する必要があります。
<manifest ...>
...
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>
...
</manifest>
地図を描画するには、追加の権限が必要です。
MapWithContentTemplate に移行する
自動車向けアプリの API レベル 7 以降、MapTemplate
、PlaceListNavigationTemplate
、RoutePreviewNavigationTemplate
は非推奨になりました。非推奨のテンプレートは引き続きサポートされますが、MapWithContentTemplate
に移行することを強くおすすめします。
これらのテンプレートによって提供される機能は、MapWithContentTemplate
を使用して実装できます。例として、次のスニペットをご覧ください。
MapTemplate
Kotlin
// MapTemplate (deprecated) val template = MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(mapController) .build()
Java
// MapTemplate (deprecated) MapTemplate template = new MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) build()) .setActionStrip(actionStrip) .setMapController(mapController) .build();
PlaceListNavigationTemplate
Kotlin
// PlaceListNavigationTemplate (deprecated) val template = PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// PlaceListNavigationTemplate (deprecated) PlaceListNavigationTemplate template = new PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
RoutePreviewNavigationTemplate
Kotlin
// RoutePreviewNavigationTemplate (deprecated) val template = RoutePreviewNavigationTemplate.Builder() .setItemList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .build()) .build()) .setHeader(header) .setNavigateAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .addAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .build()) .build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// RoutePreviewNavigationTemplate (deprecated) RoutePreviewNavigationTemplate template = new RoutePreviewNavigationTemplate.Builder() .setItemList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .build()) .build()) .setHeader(header) .setNavigateAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .addAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .build()) .build())) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
ナビゲーション メタデータを伝える
ナビゲーション アプリは、ホストに追加のナビゲーション メタデータを伝える必要があります。ホストはこの情報を使用して、車のヘッドユニットに情報を伝え、共有リソース上のナビゲーション アプリ間の競合を防ぎます。
ナビゲーション メタデータは、CarContext
からアクセスできる NavigationManager
自動車サービスより取得できます。
Kotlin
val navigationManager = carContext.getCarService(NavigationManager::class.java)
Java
NavigationManager navigationManager = carContext.getCarService(NavigationManager.class);
ナビゲーションを開始、終了、停止する
ホストが複数のナビゲーション アプリ、経路通知、自動車クラスタデータを管理するためには、ナビゲーションの現在の状態を把握している必要があります。ユーザーがナビゲーションを開始したときには、NavigationManager.navigationStarted
を呼び出します。同様に、ユーザーが目的地に到着したときや、ナビゲーションをキャンセルしたときなど、ナビゲーションを終了したときには、NavigationManager.navigationEnded
を呼び出します。
NavigationManager.navigationEnded
はユーザーがナビゲーションを終了したときのみ、呼び出します。たとえば、ルートの途中で経路を再計算する必要がある場合は、Trip.Builder.setLoading(true)
を使用します。
状況によって、ホストは、アプリによるナビゲーションの停止を必要とします。このような場合、NavigationManager.setNavigationManagerCallback
を使用してアプリが提供する NavigationManagerCallback
オブジェクトの onStopNavigation
が呼び出されます。この場合、アプリはクラスタ ディスプレイ、ナビゲーション通知、音声案内で、次のターンの情報の提供を停止する必要があります。
ルート情報を更新する
ナビゲーション使用時に、NavigationManager.updateTrip
を呼び出します。この呼び出しで提供される情報は、車のクラスタ ディスプレイとヘッドアップ ディスプレイで使用される場合があります。運転中の車の種類によっては、すべての情報がユーザーに表示されるわけではありません。たとえば、デスクトップ ヘッドユニット(DHU)には、Trip
に追加された Step
は表示されますが、Destination
情報は表示されません。
クラスタ ディスプレイに描画する
車のクラスタ ディスプレイに基本的なメタデータを表示するだけでなく、さらに臨場感のあるユーザー エクスペリエンスを提供したい場合があります。自動車向けアプリの API レベル 6 以降では、ナビゲーション アプリに独自のコンテンツをクラスタ ディスプレイ(サポートしている車の場合)に直接レンダリングするオプションがあります。ただし、次の制限があります。
- クラスタ ディスプレイ API は入力コントロールをサポートしていません。
- 自動車向けアプリの品質に関するガイドライン
NF-9
: クラスタ ディスプレイには地図タイルのみが表示されます。これらのタイルには、使用中のナビゲーション ルートを表示することもできます。 - クラスタ ディスプレイ API は
NavigationTemplate
の使用のみをサポートしています。- メイン ディスプレイとは異なり、クラスタ ディスプレイにはターンバイターン方式の指示、ETA カード、アクションなどの
NavigationTemplate
の UI 要素がすべて、常に表示されるとは限りません。地図タイルのみが常に表示される UI 要素です。
- メイン ディスプレイとは異なり、クラスタ ディスプレイにはターンバイターン方式の指示、ETA カード、アクションなどの
クラスタのサポートを宣言する
アプリがクラスタ ディスプレイでのレンダリングをサポートしていることをホストアプリに伝えるには、次のスニペットのとおり、androidx.car.app.category.FEATURE_CLUSTER
<category>
要素を CarAppService
の <intent-filter>
に追加する必要があります。
<application> ... <service ... android:name=".MyNavigationCarAppService" android:exported="true"> <intent-filter> <action android:name="androidx.car.app.CarAppService" /> <category android:name="androidx.car.app.category.NAVIGATION"/> <category android:name="androidx.car.app.category.FEATURE_CLUSTER"/> </intent-filter> </service> ... </application>
ライフサイクルと状態管理
API レベル 6 以降、自動車アプリのライフサイクル フローに変更はありませんが、CarAppService::onCreateSession
は作成された Session
に関する追加情報を提供する SessionInfo
タイプのパラメータを取得するようになりました(すなわち、ディスプレイのタイプとサポートされているテンプレートのセット)。
アプリでは同じ Session
クラスを使用して、クラスタとメイン ディスプレイの両方を処理するか、ディスプレイ固有の Sessions
を作成して、各ディスプレイの動作をカスタマイズするか(以下のスニペットをご覧ください)を選択できます。
Kotlin
override fun onCreateSession(sessionInfo: SessionInfo): Session { return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) { ClusterSession() } else { MainDisplaySession() } }
Java
@Override @NonNull public Session onCreateSession(@NonNull SessionInfo sessionInfo) { if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_CLUSTER) { return new ClusterSession(); } else { return new MainDisplaySession(); } }
クラスタ ディスプレイが表示されるタイミングと表示されるか否かは確実ではありません。また、クラスタ Session
が唯一の Session
になる可能性もあります(たとえば、ユーザーがアプリでナビゲーションを使用中に、メイン ディスプレイを他のアプリに切り替えた場合)。「標準」の利用規約では、アプリは NavigationManager::navigationStarted
が呼び出された後にのみ、クラスタ ディスプレイをコントロールできるようになります。ただし、ナビゲーションを使用していないときにアプリがクラスタ ディスプレイに表示される場合や、クラスタ ディスプレイにまったく表示されない場合もあります。アプリのアイドル状態時の地図タイルをレンダリングして、このようなシナリオに対応するかどうかはアプリによります。
ホストは Session
ごとに別々のバインダと CarContext
インスタンスを作成します。つまり、ScreenManager::push
や Screen::invalidate
などのメソッドを使用する場合、呼び出し元の Session
のみが影響を受けます。Session
をまたぐ通信が必要な場合、アプリはこれらのインスタンス間の独自の通信チャネルを作成する必要があります(たとえば、ブロードキャストや共有シングルトンなどを使用)。
クラスタのサポートをテストする
Android Auto と Android Automotive OS の両方で実装をテストできます。Android Auto の場合は、デスクトップ ヘッドユニットを構成して、セカンダリ クラスタ ディスプレイをエミュレートすることで、テストできます。Android Automotive OS の場合は、API レベル 30 以上の Generic System Image でクラスタ ディスプレイをエミュレートします。
テキストまたはアイコンを使用して TravelEstimate をカスタマイズする
推定所要時間をテキスト、アイコン、またはその両方を使ってカスタマイズするには、TravelEstimate.Builder
クラスの setTripIcon
または setTripText
メソッドを使用します。NavigationTemplate
では TravelEstimate
を使用して、予定到着時刻、残り時間、残りの距離とともに(またはその代わりに)テキストやアイコンを表示することもできます。
次のスニペットでは、setTripIcon
と setTripText
を使って推定所要時間をカスタマイズしています。
Kotlin
TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build()
Java
new TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...)) ... .setTripIcon(CarIcon.Builder(...).build()) .setTripText(CarText.create(...)) .build();
ターンバイターン通知を提供する
頻繁に更新されるナビゲーション通知を使用して、ターンバイターン(TBT)ナビゲーション指示を提供します。車の画面でナビゲーション通知として処理されるようにするには、通知ビルダーで次のことを行う必要があります。
NotificationCompat.Builder.setOngoing
メソッドを使用して、通知を進行中としてマークします。- 通知のカテゴリを
Notification.CATEGORY_NAVIGATION
に設定します。 CarAppExtender
を使用して通知を拡張します。
ナビゲーション通知は車の画面下部にあるレール ウィジェットに表示されます。通知の重要度レベルが IMPORTANCE_HIGH
に設定されている場合は、ヘッドアップ通知(HUN)としても表示されます。CarAppExtender.Builder.setImportance
メソッドで重要度が設定されていない場合は、通知チャンネルの重要度が使用されます。
CarAppExtender
で PendingIntent
を設定すると、ユーザーが HUN またはレール ウィジェットをタップしたときにアプリに送信されるようにできます。
値が true
の NotificationCompat.Builder.setOnlyAlertOnce
が呼び出された場合、重要度の高い通知は HUN で 1 回のみアラートとして表示されます。
次のスニペットは、ナビゲーション通知を作成する方法を示しています。
Kotlin
NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), Intent(ACTION_OPEN_APP).setComponent( ComponentName(context, MyNotificationReceiver::class.java)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build()
Java
new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) ... .setOnlyAlertOnce(true) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_NAVIGATION) .extend( new CarAppExtender.Builder() .setContentTitle(carScreenTitle) ... .setContentIntent( PendingIntent.getBroadcast( context, ACTION_OPEN_APP.hashCode(), new Intent(ACTION_OPEN_APP).setComponent( new ComponentName(context, MyNotificationReceiver.class)), 0)) .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH) .build()) .build();
距離の変化に応じて TBT 通知を定期的に更新します。これにより、レール ウィジェットが更新され、通知は HUN としてのみ表示されます。CarAppExtender.Builder.setImportance
で通知の重要度を設定することにより、HUN の動作を制御できます。重要度を IMPORTANCE_HIGH
に設定すると、HUN が表示されます。他の値に設定した場合は、レール ウィジェットのみ更新されます。
PlaceListNavigationTemplate コンテンツを更新する
ドライバーが PlaceListNavigationTemplate
で作成された場所のリストを閲覧しながら、ボタンをタップしてコンテンツを更新できるようにします。リストの更新を有効にするには、OnContentRefreshListener
インターフェースの onContentRefreshRequested
メソッドを実装し、PlaceListNavigationTemplate.Builder.setOnContentRefreshListener
を使用して、テンプレートにリスナーを設定します。
次のスニペットは、テンプレートにリスナーを設定する方法を示しています。
Kotlin
PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate() } .build()
Java
new PlaceListNavigationTemplate.Builder() ... .setOnContentRefreshListener(() -> { // Execute any desired logic ... // Then call invalidate() so onGetTemplate() is called again invalidate(); }) .build();
更新ボタンは、リスナーに値がある場合にのみ、PlaceListNavigationTemplate
のヘッダーに表示されます。
ユーザーが更新ボタンをクリックすると、OnContentRefreshListener
実装の onContentRefreshRequested
メソッドが呼び出されます。onContentRefreshRequested
内で、Screen.invalidate
メソッドを呼び出します。次に、ホストはアプリの Screen.onGetTemplate
メソッドにコールバックして、更新されたコンテンツを含むテンプレートを取得します。テンプレートの更新について詳しくは、テンプレートのコンテンツを更新するをご覧ください。onGetTemplate
から返される次のテンプレートが同じタイプであれば、更新としてカウントされ、テンプレート割り当てにはカウントされません。
音声によるガイダンスを提供する
車載スピーカーでナビゲーション ガイダンスを再生するには、アプリで音声フォーカスをリクエストする必要があります。AudioFocusRequest
の一部として、使用量を AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
として設定します。フォーカス ゲインも AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
として設定します。
ナビゲーションをシミュレートする
Google Play ストアへのアプリの送信時に、アプリのナビゲーション機能を検証するために、アプリで NavigationManagerCallback.onAutoDriveEnabled
コールバックを実装する必要があります。このコールバックが呼び出されると、ユーザーがナビゲーションを開始したときに、選択された目的地へのナビゲーションをシミュレートする必要があります。現在の Session
のライフサイクルが Lifecycle.Event.ON_DESTROY
状態になると、アプリはこのモードを終了できます。
コマンドラインから以下を実行することで、onAutoDriveEnabled
の実装が呼び出されることをテストできます。
adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE
例は次のとおりです。
adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE
デフォルトのナビゲーション自動車アプリ
Android Auto では、デフォルトのナビゲーション自動車アプリは、ユーザーが最後に起動したナビゲーション アプリに対応します。ユーザーがアシスタントを介してナビゲーション コマンドを呼び出した場合や、他のアプリがナビゲーションを開始するインテントを送信した場合に、デフォルトのアプリがナビゲーション インテントを受信します。
コンテキスト内のナビゲーション アラートを表示する
Alert
は、ナビゲーション画面のコンテキストを離れることなく、ドライバーに重要な情報をオプションのアクションとともに表示します。ドライバーに優れたエクスペリエンスを提供するために、Alert
は NavigationTemplate
内で機能し、ルートのナビゲーションを妨げることなく、できる限りドライバーが注意散漫にならないようにします。
Alert
は NavigationTemplate
内でのみ使用できます。NavigationTemplate
の外部でユーザーに通知するには、通知を表示するの説明のとおり、ヘッドアップ通知(HUN)の使用を検討してください。
たとえば、Alert
を使用して次のことができます。
- 交通状況の変化など、現在のナビゲーションに関連する最新情報をドライバーに通知します。
- ドライバーに現在のナビゲーションに関する最新情報(スピード違反取締の有無など)を確認します。
- ドライバーが途中で誰かを迎えに行くかどうかなど、今後のタスクを提案し、ドライバーがそのタスクを実行するかどうかを確認します。
基本的な形式では、Alert
はタイトルと Alert
継続時間で構成されます。継続時間は進行状況バーで表されます。必要に応じて、サブタイトル、アイコン、最大 2 つの Action
オブジェクトを追加できます。
Alert
が表示されると、ドライバーの操作によって NavigationTemplate
から離れても、その情報が別のテンプレートに引き継がれることはありません。Alert
がタイムアウトするか、ユーザーが操作を行うか、アプリが Alert
を解除するまで、元の NavigationTemplate
が維持されます。
アラートを作成する
Alert.Builder
を使用して、Alert
インスタンスを作成します。
Kotlin
Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build()
Java
new Alert.Builder( /*alertId*/ 1, /*title*/ CarText.create("Hello"), /*durationMillis*/ 5000 ) // The fields below are optional .addAction(firstAction) .addAction(secondAction) .setSubtitle(CarText.create(...)) .setIcon(CarIcon.APP_ICON) .setCallback(...) .build();
Alert
のキャンセルまたは解除をリッスンするには、AlertCallback
インターフェースの実装を作成します。AlertCallback
の呼び出しパスは次のとおりです。
Alert
がタイムアウトした場合、ホストはAlertCallback.onCancel
メソッドをAlertCallback.REASON_TIMEOUT
の値を指定して呼び出します。その後AlertCallback.onDismiss
メソッドを呼び出します。ドライバーが操作ボタンのいずれかをクリックすると、ホストは
Action.OnClickListener
を呼び出してから、AlertCallback.onDismiss
を呼び出します。Alert
がサポートされていない場合、ホストはAlertCallback.onCancel
をAlertCallback.REASON_NOT_SUPPORTED
の値を指定して呼び出します。Alert
は表示されていないため、ホストはAlertCallback.onDismiss
を呼び出しません。
アラート時間を構成する
アプリのニーズに合わせて、Alert
の時間を選択します。ナビゲーション Alert
の推奨時間は 10 秒です。詳しくは、ナビゲーション アラートをご覧ください。
アラートを表示する
Alert
を表示するには、アプリの CarContext
から利用できる AppManager.showAlert
メソッドを呼び出します。
// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)
- 現在表示されている
Alert
の ID と同じalertId
のAlert
でshowAlert
を呼び出すと、何も起こりません。Alert
は更新されません。Alert
を更新するには、新しいalertId
で再作成する必要があります。 - 現在表示されている
Alert
とは違うalertId
のAlert
でshowAlert
を呼び出すと、現在表示されているAlert
が終了します。
アラートを解除する
Alert
はタイムアウトやドライバーの操作により自動的に解除されますが、情報が古くなった場合などに Alert
を手動で解除することもできます。Alert
を解除するには、その Alert
の alertId
で dismissAlert
メソッドを呼び出します。
// Dismiss the same alert
carContext.getCarService(AppManager.class).dismissAlert(alert.getId())
現在表示されている Alert
と一致しない alertId
で dismissAlert
を呼び出した場合、何も起こりません。例外はスローされません。