自動車向け Android アプリ ライブラリを使用する

自動車向け Android アプリ ライブラリを使用すると、ナビゲーション アプリ、スポット(POI)、モノのインターネット(IOT)アプリを自動車に取り込むことができます。これは、ドライバーの注意散漫に関する基準を満たすように設計された一連のテンプレートを提供し、車のさまざまな画面要素や入力モダリティなどの詳細に配慮することで実現しています。

このガイドでは、ライブラリの主な機能とコンセプトの概要を示し、シンプルなアプリを設定するプロセスについて説明します。詳しい手順については、自動車向けアプリ ライブラリの基礎に関する Codelab をご覧ください。

始める前に

  1. 自動車向けアプリ ライブラリに関する運転のための設計ページを確認する
  2. 次のセクションの主な用語とコンセプトを確認してください。
  3. Android Auto システム UIAndroid Automotive OS の設計を十分に理解します。
  4. リリースノートを確認してください。
  5. サンプルを確認してください。

主な用語と概念

モデルとテンプレート
ユーザー インターフェースは、所属するテンプレートで許可されているように、さまざまな方法で配置できるモデル オブジェクトのグラフで表されます。テンプレートは、これらのグラフのルートとして機能するモデルのサブセットです。モデルには、テキストや画像の形でユーザーに表示される情報と、そのような情報の外観を構成する属性(テキストの色や画像サイズなど)が含まれます。ホストはモデルを、ドライバーの注意散漫に関する基準を満たすように設計されたビューに変換し、車のさまざまな画面要素や入力モダリティなどの詳細を処理します。
ホスト
ホストは、ライブラリの API によって提供される機能を実装するバックエンド コンポーネントであり、アプリを車内で実行できます。ホストの役割は、アプリの検出とそのライフサイクルの管理から、モデルのビューへの変換やアプリへのユーザー操作の通知まで多岐にわたります。モバイル デバイスでは、このホストは Android Auto によって実装されます。Android Automotive OS では、このホストはシステムアプリとしてインストールされます。
テンプレートの制限事項
テンプレートに応じて、そのモデルのコンテンツに制限が課されます。たとえば、リスト テンプレートには、ユーザーに提示できるアイテム数に上限があります。テンプレートには、タスクのフローを形成するための接続方法にも制限があります。たとえば、アプリは画面スタックに最大 5 つのテンプレートのみをプッシュできます。詳しくは、テンプレートの制限をご覧ください。
Screen
Screen は、ユーザーに提示されるユーザー インターフェースを管理するためにアプリが実装するライブラリによって提供されるクラスです。Screen にはライフサイクルがあり、画面が表示されているときに表示するテンプレートをアプリが送信するメカニズムを提供します。Screen インスタンスは、Screen スタックとの間で push およびポップすることもできます。これにより、テンプレート フローの制限を遵守できます。
CarAppService
CarAppService は抽象 Service クラスです。ホストによって検出、管理されるためには、アプリに実装とエクスポートが必要です。アプリの CarAppService は、createHostValidator を使用してホスト接続が信頼できるかどうかを検証し、その後、onCreateSession を使用して接続ごとに Session インスタンスを提供します。
Session

Session は、アプリが CarAppService.onCreateSession を使用して実装し返す必要がある抽象クラスです。車の画面に情報を表示するためのエントリ ポイントとして機能します。アプリの表示 / 非表示など、車載画面でのアプリの現在の状態を通知するライフサイクルがあります。

アプリの初回起動時など、Session が起動されると、ホストは onCreateScreen メソッドを使用して最初の Screen の表示をリクエストします。

自動車向けアプリ ライブラリをインストールする

ライブラリをアプリに追加する方法については、Jetpack ライブラリのリリースページをご覧ください。

アプリのマニフェスト ファイルを構成する

自動車向けアプリを作成する前に、アプリのマニフェスト ファイルを次のように構成します。

CarAppService を宣言する

ホストは CarAppService の実装を介してアプリに接続します。マニフェストでこのサービスを宣言して、ホストがアプリを検出して接続できるようにします。

また、アプリのインテント フィルタの <category> 要素でアプリのカテゴリを宣言する必要もあります。この要素で使用できる値については、サポートされているアプリのカテゴリのリストをご覧ください。

次のコード スニペットは、マニフェストでスポットアプリの自動車向けアプリサービスを宣言する方法を示しています。

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

サポートされているアプリのカテゴリ

前のセクションで説明したように、CarAppService を宣言する際に、インテント フィルタに以下のカテゴリ値を 1 つ以上追加して、アプリのカテゴリを宣言します。

各カテゴリと、各カテゴリに属するアプリの条件の詳細については、自動車向け Android アプリの品質をご覧ください。

アプリの名前とアイコンを指定する

ホストがシステム UI でアプリを表すために使用するアプリ名とアイコンを指定する必要があります。

CarAppServicelabel 属性と icon 属性を使用して、アプリを表すために使用するアプリ名とアイコンを指定できます。

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

ラベルまたはアイコンが <service> 要素で宣言されていない場合、ホストは <application> 要素に指定された値にフォールバックします。

カスタムテーマを設定する

自動車向けアプリのカスタムテーマを設定するには、マニフェスト ファイルに次のように <meta-data> 要素を追加します。

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

次に、スタイル リソースを宣言して、カスタムの自動車アプリのテーマに次の属性を設定します。

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

自動車向けアプリの API レベル

自動車向けアプリ ライブラリでは独自の API レベルが定義されており、自動車のテンプレート ホストでサポートされているライブラリ機能を確認できます。ホストでサポートされている自動車向けアプリの最も高い API レベルを取得するには、getCarAppApiLevel() メソッドを使用します。

アプリがサポートする自動車向けアプリの最小 API レベルを AndroidManifest.xml ファイルで宣言します。

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

下位互換性を維持し、機能の使用に必要な最小 API レベルを宣言する方法の詳細については、RequiresCarApi アノテーションのドキュメントをご覧ください。自動車向けアプリ ライブラリの特定の機能を使用するために必要な API レベルの定義については、CarAppApiLevels のリファレンス ドキュメントをご覧ください。

CarAppService とセッションを作成する

アプリは CarAppService クラスを拡張して、ホストへの現在の接続に対応する Session インスタンスを返す onCreateSession メソッドを実装する必要があります。

Kotlin

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Java

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

Session インスタンスは、アプリの初回起動時に使用する Screen インスタンスを返します。

Kotlin

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Java

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

自動車用アプリをアプリのホーム画面やランディング スクリーン以外の画面から開始する必要があるシナリオ(ディープリンクの処理など)を処理するには、onCreateScreen から戻る前に ScreenManager.push を使用して画面のバックスタックを事前シードできます。事前に埋め込むことで、ユーザーはアプリによって表示された最初の画面から前の画面に戻ることができます。

起動画面を作成する

アプリが表示する画面を作成するには、Screen クラスを拡張するクラスを定義し、その onGetTemplate メソッドを実装します。このメソッドは、車の画面に表示される UI の状態を表す Template インスタンスを返します。

次のスニペットは、PaneTemplate テンプレートを使用してシンプルな「Hello world!」文字列を表示する Screen を宣言する方法を示しています。

Kotlin

class HelloWorldScreen(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()
    }
}

Java

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

CarContext クラス

CarContext クラスは、Session インスタンスと Screen インスタンスからアクセスできる ContextWrapper サブクラスです。カーサービスには、画面スタックを管理する ScreenManager、一般的なアプリ関連機能(ナビゲーション アプリの地図の描画のための Surface オブジェクトへのアクセスなど)のための AppManager、ターンバイターン ナビゲーション アプリがナビゲーション関連のメタデータと通信するために使用する NavigationManager などのカーサービスへのアクセスを提供します。

ナビゲーション アプリで使用できるライブラリ機能の包括的なリストについては、ナビゲーション テンプレートにアクセスするをご覧ください。

CarContext には、車載画面から構成を使用してドローアブル リソースを読み込む、インテントを使用して車内でアプリを起動する、ナビゲーション アプリが地図をダークモードで表示するかどうかを通知するなど、他の機能もあります。

画面ナビゲーションを実装する

多くの場合、アプリは複数の異なる画面を提示し、それぞれが、画面に表示されたインターフェースを操作しながら移動できるさまざまなテンプレートを使用している可能性があります。

ScreenManager クラスは、ユーザーが車の画面で戻るボタンを選択したときや、一部の自動車で利用可能なハードウェアの戻るボタンを使用したときに、自動的にポップできる画面をプッシュするために使用できる画面スタックを提供します。

次のスニペットは、メッセージ テンプレートに「戻る」アクションと、ユーザーが選択したときに新しい画面をプッシュするアクションを追加する方法を示しています。

Kotlin

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Java

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

Action.BACK オブジェクトは、自動的に ScreenManager.pop を呼び出す標準的な Action です。この動作は、CarContext から入手できる OnBackPressedDispatcher インスタンスを使用してオーバーライドできます。

運転中もアプリを安全に使用できるように、画面スタックの最大深度は 5 画面までです。詳しくは、テンプレートの制限のセクションをご覧ください。

テンプレートのコンテンツを更新する

アプリは Screen.invalidate メソッドを呼び出すことで、Screen の内容の無効化をリクエストできます。その後、ホストはアプリの Screen.onGetTemplate メソッドにコールバックして、新しいコンテンツを含むテンプレートを取得します。

Screen を更新するときは、ホストが新しいテンプレートをテンプレート割り当てにカウントしないように、テンプレート内の更新可能な特定のコンテンツを理解することが重要です。詳しくは、テンプレートの制限のセクションをご覧ください。

Screen と、その onGetTemplate の実装によって返されるテンプレートのタイプとが 1 対 1 でマッピングされるように画面を構成することをおすすめします。

ユーザーとやり取りする

アプリは、モバイルアプリに似たパターンを使用してユーザーとやり取りできます。

ユーザー入力を処理する

アプリは、適切なリスナーをそれらをサポートするモデルに渡すことで、ユーザー入力に応答できます。次のスニペットは、アプリのコードで定義されたメソッドにコールバックする OnClickListener を設定する Action モデルの作成方法を示しています。

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

onClickNavigate メソッドは、CarContext.startCarApp メソッドを使用してデフォルトのナビゲーション自動車アプリを起動できます。

Kotlin

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Java

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

ACTION_NAVIGATE インテントの形式など、アプリを起動する方法については、インテントを使用して自動車アプリを起動するのセクションをご覧ください。

インタラクションの続きをモバイル デバイスで行うようにユーザーをガイドするアクションなど、特定のアクションは車がパーキング状態にあるときに限り許可されます。これらのアクションを実装するには、ParkedOnlyOnClickListener を使用します。車が駐車されていない場合、ホストはユーザーにアクションが許可されていない旨を表示します。駐車されている場合、コードは通常どおり実行されます。次のスニペットは、ParkedOnlyOnClickListener を使用してモバイル デバイスで設定画面を開く方法を示しています。

Kotlin

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Java

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

通知を表示する

モバイル デバイスに送信された通知は、CarAppExtender で拡張された場合にのみ、車の画面に表示されます。コンテンツ タイトル、テキスト、アイコン、アクションなどの一部の通知属性は CarAppExtender で設定でき、車の画面に表示されるときに通知の属性をオーバーライドできます。

次のスニペットは、モバイル デバイスとは異なるタイトルで、通知を車の画面に送信する方法を示しています。

Kotlin

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Java

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

通知は、ユーザー インターフェースの次の部分に影響する場合があります。

  • ヘッドアップ通知(HUN)がユーザーに表示されることがあります。
  • 通知センターにエントリが追加されたり、必要に応じて、レールにバッジが表示されたりすることがあります。
  • ナビゲーション アプリの場合、ターンバイターン通知で説明されているように、通知はレール ウィジェットに表示される場合があります。

CarAppExtender のドキュメントに記載されているように、通知の優先度を使用して、これらの各ユーザー インターフェース要素に影響するようにアプリの通知を構成できます。

値が trueNotificationCompat.Builder.setOnlyAlertOnce が呼び出された場合、優先度の高い通知が HUN として 1 回だけ表示されます。

自動車向けアプリの通知を設計する方法について詳しくは、Google の運転向けデザインガイドの通知をご覧ください。

トーストを表示する

アプリでは、次のスニペットに示すように、CarToast を使用してトーストを表示できます。

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

権限をリクエストする

アプリが制限付きのデータまたはアクション(位置情報など)にアクセスする必要がある場合は、Android 権限の標準ルールが適用されます。権限をリクエストするには、CarContext.requestPermissions() メソッドを使用します。

標準の Android API を使用するのではなく CarContext.requestPermissions() を使用する利点は、権限ダイアログを作成するために独自の Activity を起動する必要がないことです。さらに、Android Auto と Android Automotive OS の両方で同じコードを使用できます。プラットフォームに依存するフローを作成する必要はありません。

Android Auto での権限ダイアログのスタイル設定

Android Auto では、ユーザー向けの権限ダイアログがスマートフォンに表示されます。デフォルトでは、ダイアログの背後に背景はありません。カスタム背景を設定するには、AndroidManifest.xml ファイルで自動車向けアプリのテーマを宣言し、自動車向けアプリのテーマの carPermissionActivityLayout 属性を設定します。

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

次に、自動車向けアプリのテーマの carPermissionActivityLayout 属性を設定します。

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

インテントを使用して自動車アプリを起動する

CarContext.startCarApp メソッドを呼び出すと、次のいずれかのアクションを実行できます。

次の例は、駐車予約の詳細を表示する画面でアプリを開く、というアクションを含む通知を作成する方法を示しています。アプリのアクションに対する明示的インテントをラップする PendingIntent を含むコンテンツ インテントを使用して、通知インスタンスを拡張します。

Kotlin

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Java

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

また、ユーザーが通知インターフェースでアクションを選択し、データ URI を含むインテントで CarContext.startCarApp を呼び出したときにインテントを処理するために呼び出される BroadcastReceiver も宣言する必要があります。

Kotlin

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Java

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

最後に、アプリの Session.onNewIntent メソッドがこのインテントを処理するために、駐車予約画面をスタックにプッシュします(まだ一番上になっていない場合)。

Kotlin

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Java

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

自動車アプリの通知を処理する方法については、通知を表示するセクションをご覧ください。

テンプレートの制限事項

ホストは、特定のタスクに表示するテンプレート数を最大 5 つに制限します。そのうち最後のテンプレートは、次のいずれかのタイプにする必要があります。

この上限はテンプレートの数に適用されます。スタック内の Screen インスタンスの数には適用されません。たとえば、アプリが画面 A で 2 つのテンプレートを送信し、その後画面 B をプッシュした場合、さらに 3 つのテンプレートを送信できます。または、各画面が 1 つのテンプレートを送信するように構成されている場合、アプリは 5 つの画面インスタンスを ScreenManager スタックに push できます。

これらの制限には、テンプレートの更新、戻る操作、リセット操作などの特殊なケースがあります。

テンプレートの更新

一部のコンテンツの更新は、テンプレートの上限にカウントされません。一般に、アプリが以前のテンプレートと同じタイプの新しいテンプレートを push する場合、前のテンプレートと同じメイン コンテンツが含まれている場合、新しいテンプレートは割り当てにカウントされません。たとえば、ListTemplate 内で行の切り替え状態を更新しても、割り当てに対するカウントは行われません。どのような種類のコンテンツ更新がテンプレートの更新としてカウントされるかについて詳しくは、個々のテンプレートのドキュメントをご覧ください。

戻る操作

タスク内のサブフローを有効にするために、ホストはアプリが ScreenManager スタックから Screen をポップしたことを検出し、アプリが戻るテンプレートの数に基づいて残りの割り当てを更新します。

たとえば、アプリが画面 A で 2 つのテンプレートを送信し、画面 B をプッシュしてさらに 2 つのテンプレートを送信した場合、アプリには 1 つの割り当てが残っています。その後、アプリが画面 A に戻った場合、ホストは割り当てを 3 にリセットします。これは、アプリが 2 つのテンプレート分戻ったためです。

画面にポップバックするとき、アプリはその画面で最後に送信されたテンプレートと同じタイプのテンプレートを送信する必要があります。他のタイプのテンプレートを送信すると、エラーが発生します。ただし、戻る操作時にタイプが同じのままであれば、アプリは割り当てに影響を与えることなくテンプレートのコンテンツを自由に変更できます。

リセット操作

一部のテンプレートには、タスクの終了を示す特別なセマンティクスがあります。たとえば、NavigationTemplate は画面上に留まり、ユーザーの消費のために新しいターンバイターンの指示で更新されることが期待されるビューです。これらのテンプレートのいずれかに到達すると、ホストはテンプレートの割り当てをリセットし、そのテンプレートを新しいタスクの最初のステップとして扱います。これにより、アプリで新しいタスクを開始できるようになります。ホスト上でリセットがトリガーされるテンプレートについては、個々のテンプレートのドキュメントをご覧ください。

ホストが通知アクションまたはランチャーからアプリを起動するインテントを受信すると、割り当てもリセットされます。このメカニズムにより、アプリは通知から新しいタスクフローを開始できるようになります。これは、アプリがすでにバインドされていてフォアグラウンドにある場合にも当てはまります。

車の画面にアプリの通知を表示する方法について詳しくは、通知を表示するセクションをご覧ください。通知アクションからアプリを起動する方法については、インテントを使用して自動車アプリを起動するのセクションをご覧ください。

Connection API

アプリが Android Auto と Android Automotive OS のどちらで実行されているかを確認するには、CarConnection API を使用して実行時に接続情報を取得します。

たとえば、自動車アプリの SessionCarConnection を初期化し、LiveData の更新に登録します。

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

オブザーバーで、接続状態の変化に対応できます。

Kotlin

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Java

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

Constraints API

車によって、ユーザーに同時に表示できる Item インスタンスの数は異なります。ConstraintManager を使用して、実行時にコンテンツの上限を確認し、テンプレートに適切なアイテム数を設定します。

まず、CarContext から ConstraintManager を取得します。

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

取得した ConstraintManager オブジェクトをクエリして、関連するコンテンツの上限を取得できます。たとえば、グリッドに表示できるアイテムの数を取得するには、CONTENT_LIMIT_TYPE_GRID を指定して getContentLimit を呼び出します。

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

ログインフローを追加する

アプリがユーザーにログイン エクスペリエンスを提供する場合、自動車向けアプリの API レベル 2 以上で SignInTemplateLongMessageTemplate などのテンプレートを使用すると、車のヘッドユニットでアプリへのログインを処理できます。

SignInTemplate を作成するには、SignInMethod を定義します。自動車向けアプリ ライブラリは現在、次のログイン方法をサポートしています。

  • InputSignInMethod: ユーザー名とパスワードによるログイン用。
  • PIN ログイン用の PinSignInMethod。ユーザーはヘッドユニットに表示される PIN を使用してスマートフォンからアカウントをリンクします。
  • ProviderSignInMethod: Google ログインOne Tap などのプロバイダのログインに使用します。
  • QRCodeSignInMethod: QR コードによるログイン。ユーザーは QR コードをスキャンしてスマートフォンでログインを完了します。これは Car API レベル 4 以降で使用できます。

たとえば、ユーザーのパスワードを収集するテンプレートを実装するには、まず InputCallback を作成してユーザー入力の処理と検証を行います。

Kotlin

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Java

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

InputSignInMethod Builder には InputCallback が必要です。

Kotlin

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Java

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

最後に、新しい InputSignInMethod を使用して SignInTemplate を作成します。

Kotlin

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Java

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

AccountManager を使用する

認証が必要な Android Automotive OS アプリでは、次の理由により AccountManager を使用する必要があります。

  • ユーザー エクスペリエンスの向上と簡単なアカウント管理: ユーザーは、システム設定のアカウント メニューから、すべてのアカウントを簡単に管理できます(ログインとログアウトを含む)。
  • 「ゲスト」エクスペリエンス: 自動車は共有デバイスであるため、OEM は車両でゲスト エクスペリエンスを有効にして、アカウントを追加することはできません。

テキスト文字列のバリエーションを追加する

自動車の画面サイズに応じて、表示されるテキストの量は異なります。自動車向けアプリの API レベル 2 以上では、画面に合う最適なテキスト文字列のバリエーションを複数指定できます。テキストのバリエーションを使用できる場所を確認するには、CarText を使用するテンプレートとコンポーネントを探してください。

CarText.Builder.addVariant() メソッドを使用して、CarText にテキスト文字列のバリアントを追加できます。

Kotlin

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Java

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

この CarText は、たとえば GridItem のプライマリ テキストとして使用できます。

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

文字列を優先度の高いものから順に追加します(例: 長いものから短いもの)。ホストは、車載画面で利用可能なスペースに応じて適切な長さの文字列を選択します。

行にインライン CarIcons を追加する

CarIconSpan を使用してアイコンをテキストとともに追加することで、アプリの視覚的な魅力を高めることができます。これらのスパンの作成の詳細については、CarIconSpan.create のドキュメントをご覧ください。スパンを使用したテキスト スタイルの仕組みの概要については、Spantastic text styling with Spans をご覧ください。

Kotlin

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Java

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

Car Hardware API

自動車向けアプリの API レベル 3 以降、自動車向けアプリ ライブラリには、車両のプロパティとセンサーへのアクセスに使用できる API が用意されています。

要件

Android Auto で API を使用するには、まず Android Auto モジュールの build.gradle ファイルに androidx.car.app:app-projected への依存関係を追加します。Android Automotive OS の場合、androidx.car.app:app-automotive への依存関係を Android Automotive OS モジュールの build.gradle ファイルに追加します。

また、AndroidManifest.xml ファイルで、使用するカーデータをリクエストするために必要な関連する権限を宣言する必要があります。これらの権限は、ユーザーから付与される必要があります。プラットフォームに依存するフローを作成する必要はなく、Android Auto と Android Automotive OS の両方で同じコードを使用できます。ただし、必要な権限は異なります。

車両情報

次の表に、CarInfo API で表示されるプロパティと、それらを使用するためにリクエストする必要がある権限を示します。

メソッド プロパティ Android Auto の権限 Android Automotive OS の権限
fetchModel メーカー、モデル、年 android.car.permission.CAR_INFO
fetchEnergyProfile EV コネクタの種類、燃料の種類 com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO
addTollListener
removeTollListener
通行料金カードの状態、通行料金カードの種類
addEnergyLevelListener
removeEnergyLevelListener
バッテリー残量、燃料残量、残量低下、残りの航続距離 com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY
android.car.permission.CAR_ENERGY_PORTS
android.car.permission.READ_CAR_DISPLAY_UNITS
addSpeedListener
removeSpeedListener
未加工速度、表示速度(車のクラスタ ディスプレイに表示) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED
android.car.permission.READ_CAR_DISPLAY_UNITS
addMileageListener
removeMileageListener
走行距離 com.google.android.gms.permission.CAR_MILEAGE このデータは、Google Play ストアからインストールしたアプリで Android Automotive OS では使用できません。

たとえば、残りの範囲を取得するには、CarInfo オブジェクトをインスタンス化し、OnCarDataAvailableListener を作成して登録します。

Kotlin

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Java

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

車のデータが常に利用できるとは限りません。エラーが発生した場合は、リクエストした値のステータスを確認し、リクエストしたデータを取得できなかった理由を詳しく把握します。CarInfo クラスの完全な定義については、リファレンス ドキュメントをご覧ください。

車載センサー

CarSensors クラスを使用すると、車両の加速度計、ジャイロスコープ、コンパス、位置情報のデータにアクセスできます。これらの値を使用できるかどうかは、OEM によって異なります。加速度計、ジャイロスコープ、コンパスからのデータの形式は、SensorManager API から取得する形式と同じです。たとえば、車両の向きを確認するには、次のようにします。

Kotlin

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Java

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

車から位置情報にアクセスするには、android.permission.ACCESS_FINE_LOCATION 権限を宣言してリクエストする必要もあります。

テスト

Android Auto でテストする際にセンサーデータをシミュレートするには、デスクトップ ヘッドユニット ガイドのセンサーセンサーの構成のセクションをご覧ください。Android Automotive OS でテストする際にセンサーデータをシミュレートするには、Android Automotive OS エミュレータ ガイドのハードウェアの状態のエミュレートをご覧ください。

CarAppService、Session、Screen のライフサイクル

Session クラスと Screen クラスは、LifecycleOwner インターフェースを実装します。ユーザーがアプリを操作すると、次の図に示すように、Session オブジェクトと Screen オブジェクトのライフサイクル コールバックが呼び出されます。

CarAppService とセッションのライフサイクル

図 1. Session ライフサイクル。

詳細については、Session.getLifecycle メソッドのドキュメントをご覧ください。

画面のライフサイクル

図 2. Screen ライフサイクル。

詳細については、Screen.getLifecycle メソッドのドキュメントをご覧ください。

車のマイクから録音

アプリの CarAppServiceCarAudioRecord API を使用して、ユーザーの車載マイクへのアクセス権をアプリに付与できます。ユーザーは、車載マイクにアクセスする権限をアプリに付与する必要があります。アプリは、アプリ内でのユーザー入力を記録して処理できます。

録画権限

音声を録音する前に、まず AndroidManifest.xml で録音する権限を宣言し、ユーザーに許可をリクエストする必要があります。

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

実行時に録画する権限をリクエストする必要があります。自動車向けアプリで権限をリクエストする方法の詳細については、権限をリクエストするセクションをご覧ください。

音声を記録する

ユーザーから録音の許可が得られたら、音声を録音して処理できます。

Kotlin

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

音声フォーカス

車載マイクから録音するときは、まず音声フォーカスを取得して、進行中のメディアがすべて停止されるようにします。音声フォーカスが失われた場合は 録音を停止します

音声フォーカスを取得する方法の例を次に示します。

Kotlin

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

テスト ライブラリ

自動車向け Android のテスト ライブラリには、テスト環境でのアプリの動作の検証に使用できる補助クラスが用意されています。たとえば、SessionController を使用すると、ホストへの接続をシミュレートして、正しい ScreenTemplate が作成され、返されることを確認できます。

使用例については、サンプルをご覧ください。

自動車向け Android アプリ ライブラリに関する問題を報告する

ライブラリに問題が見つかった場合は、Google Issue Tracker を使用して報告します。 問題テンプレートに必要な情報をすべて記入してください。

新しい問題を報告する

新しい問題を報告する前に、その問題がライブラリのリリースノートに記載されているか、問題リストで報告されていないかをご確認ください。Issue Tracker 内で各問題の横にあるスターアイコンをクリックすると、問題を登録して投票することができます。詳細については、問題を登録する手順をご覧ください。