Instrument Cluster API

Instrument Cluster API(Android API)を使用することで、Google マップなどのナビゲーション アプリを車内のセカンダリ ディスプレイ(ハンドルの背後の計器パネル上など)に表示できます。このページでは、そのセカンダリ ディスプレイを制御するサービスを作成する方法、およびそのサービスを CarService と統合して、ナビゲーション アプリにユーザー インターフェースを表示できるようにする方法について説明します。

用語

このページで使用される用語は次のとおりです。

CarInstrumentClusterManager
外部アプリがインストルメント クラスタでアクティビティを起動したり、インストルメント クラスタでアクティビティを表示するための準備が完了したときにコールバックを受信したりできるようにするための CarManager インスタンス。
CarManager
CarService で実装される車両固有のサービスを外部アプリで操作するために使用されるすべてのマネージャーの基本クラス。
CarService
外部アプリ(Google マップなど)と車両固有の機能(インストルメント クラスタへのアクセスなど)間の通信を提供する Android プラットフォーム サービス。
目的地
車両のナビゲーション先とする最終目的地。
予定到着時刻(ETA)
目的地の予定到着時刻。
ヘッドユニット(HU)
自動車に搭載されている主要演算ユニット。HU はすべての Android コードを実行し、車内のセントラル ディスプレイに接続されます。
インストルメント クラスタ
ハンドル背後の車載計器の間に配置されているセカンダリ ディスプレイ。これは、車の内部ネットワーク(CAN バス)経由で HU に接続された、独立した演算ユニットである場合と、HU に接続されたセカンダリ ディスプレイである場合があります。
InstrumentClusterRenderingService
インストルメント クラスタ ディスプレイとのインターフェースとして使用されるサービスの基本クラス。OEM は、OEM 固有のハードウェアとやり取りするための、このクラスの拡張機能を提供する必要があります。
KitchenSink アプリ
Android Automotive に含まれるテストアプリ。
ルート
車両が目的地に到着するための特定の経路。
シングルトン サービス
android:singleUser属性を持つ Android サービス。Android システム上で実行されるこのサービスのインスタンスは常に 1 つのみです。

前提条件

続行するには、次の要素が必要です。

  • Android 開発環境。Android 開発環境の設定については、ビルド要件を参照してください。
  • Android ソースコードをダウンロードします。https://android.googlesource.com にアクセスし、pi-car-release ブランチ(またはそれ以降)から Android ソースコードの最新バージョンを入手します。
  • ヘッドユニット(HU)Android 9 以降を実行できる Android デバイス。このデバイスは、独自のディスプレイを持ち、Android の新しいビルドでディスプレイのフラッシュを行える必要があります。
  • インストルメント クラスタには、次のいずれかを使用できます。
    • HU に接続された物理的なセカンダリ ディスプレイ。これは、デバイスのハードウェアとカーネルが複数のディスプレイの管理をサポートしている場合に限ります。
    • 独立ユニット。ネットワーク接続で HU に接続され、独自のディスプレイに受信した動画ストリームを表示できるものであれば、どのような演算ユニットでも使用できます。
    • エミュレートされたディスプレイ。開発時には、次のいずれかのエミュレート環境を使用できます。
      • シミュレートされたセカンダリ ディスプレイ。AOSP Android ディストリビューションでシミュレートされたセカンダリ ディスプレイを有効化するには、設定システムアプリの開発者向けオプション設定に移動し、[セカンダリ ディスプレイをシミュレート] を選択します。この構成は、物理的なセカンダリ ディスプレイを接続する場合と同等です。ただし、このディスプレイがプライマリ ディスプレイの上に重なって表示されるという制限があります。
      • エミュレートされたインストルメント クラスタ。AAOS に付属される Android Emulator には、ClusterRenderingService を使用してインストルメント クラスタを表示するオプションがあります。

統合アーキテクチャ

統合コンポーネント

Instrument Cluster API の統合は、次の 3 つのコンポーネントで構成されます。

  • CarService
  • ナビゲーション アプリ
  • OEM インストルメント クラスタ サービス

統合コンポーネント

CarService

CarService は、ナビゲーション アプリと自動車との間の調整を行って、任意の時点でナビゲーション アプリが 1 つだけアクティブになるようにするとともに、android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL 権限を持つアプリのみが自動車にデータを送信できるようにします。

CarService は、車両固有のすべてのサービスをブートストラップし、一連のマネージャーを通してこれらのサービスへのアクセスを提供します。自動車で実行されているアプリは、これらのマネージャーにアクセスすることでサービスを操作できます。

インストルメント クラスタを実装するには、自動車 OEM が InstrumentClusterRendererService のカスタム実装を作成し、ClusterRenderingService を更新する必要があります。

インストルメント クラスタをレンダリングすると、起動プロセス中に CarServiceClusterRenderingServiceInstrumentClusterRendererService キーを読み取って、InstrumentClusterService の実装を見つけます。AOSP では、このエントリは Navigation State API サンプル クラスタ実装レンダリング サービスを参照します。

<string name="instrumentClusterRendererService">
android.car.cluster/.ClusterRenderingService
</string>

このエントリで参照されるサービスは初期化され、CarService にバインドされます。Google マップなどのナビゲーション アプリが CarInstrumentClusterManager をリクエストすると、バインドされた InstrumentClusterRenderingService からインストルメント クラスタの状態を更新するマネージャーが CarService によって提供されます(この場合の バインドは Android サービスを指します)。

インストルメント クラスタ サービス

OEM は、ClusterRenderingService のサブクラスを含む Android Package(APK)を作成する必要があります。

このクラスには次の 2 つの目的があります。

  • Android とインストルメント クラスタ レンダリング デバイスのインターフェースを提供する(このページの目的)。
  • ターンバイターン方式のナビゲーション ガイダンスなど、ナビゲーション状態の更新を受信してレンダリングする。

最初の目的のためには、InstrumentClusterRendererService の OEM 実装によって、車室内画面での情報のレンダリングに使用されるセカンダリ ディスプレイを初期化し、InstrumentClusterRendererService.setClusterActivityOptions() メソッドと InstrumentClusterRendererService.setClusterActivityState() メソッドを呼び出してこの情報を CarService に通知する必要があります。

2 番目の目的のためには、インストルメント クラスタ サービスにより、ナビのナビゲーション状態更新イベントを受け取る ClusterRenderingService インターフェースの実装を提供する必要があります。このナビゲーション状態更新イベントは、eventType とイベントデータのバンドルとしてエンコードされます。

統合シーケンス

次の図は、更新をレンダリングするナビゲーション状態の実装を示しています。

統合シーケンス

この図では、色は以下のように示されています。

  • Android プラットフォームが提供する CarServiceCarNavigationStatusManager。詳細については、自動車CAR_NAVIGATION_SERVICE をご覧ください。
  • シアン OEM によって実装される InstrumentClusterRendererService
  • Google とサードパーティのデベロッパーが実装するナビゲーション アプリ。
  • CarAppFocusManager。詳細については、以下の CarAppFocusManager API の使用CarAppFocusManager をご覧ください。

ナビゲーション状態情報フローは、次のようなシーケンスをたどります。

  1. CarService は、InstrumentClusterRenderingService を初期化します。
  2. 初期化中に、InstrumentClusterRenderingService は以下を使用して CarService を更新します。
    1. 不鮮明な境界などの、インストルメント クラスタの表示プロパティ(不鮮明な境界に関する詳細については、後ほどご確認ください)。
    2. インストルメント クラスタのディスプレイ内でアクティビティを起動するのに必要なアクティビティ オプション。詳しくは、ActivityOptions をご覧ください。
  3. ナビゲーション アプリ(Android Automotive 向け Google マップや、必要な権限を持つ地図アプリなど)が以下を行います。
    1. car-lib から Car クラスを使用して CarAppFocusManager を取得します。
    2. ターンバイターン方式ナビを開始する前に、CarAppFocusManager.requestFocus() を呼び出して CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATIONappType パラメータとして渡します。
  4. CarAppFocusManager がこのリクエストを CarService に通知します。許可されている場合、CarService はナビゲーション アプリのパッケージを検査し、カテゴリ android.car.cluster.NAVIGATION とマークされているアクティビティを探します。
  5. 検出されると、ナビゲーション アプリは、InstrumentClusterRenderingService によってレポートされた ActivityOptions を使用してアクティビティを起動し、インストルメント クラスタの表示プロパティを付加情報としてインテント内に含めます。

API を統合する

InstrumentClusterRenderingService の実装は、次の条件を満たしている必要があります。

  • AndroidManifest.xml に次の値を追加することでシングルトン サービスとして指定されている。これは、初期化時やユーザー切り替え時であっても、実行されるインストルメント クラスタ サービスのコピーが 1 つだけであることを保証するために必要です。
    android:singleUser="true"
  • BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE システム権限を保持している。これにより、Android システム イメージの一部として含まれているインストルメント クラスタ レンダリング サービス以外のものは決して CarService によってバインドされないことが保証されます。
    <uses-permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
    

InstrumentClusterRenderingService を実装する

サービスをビルドするには:

  1. ClusterRenderingService から拡張するクラスを作成し、対応するエントリを AndroidManifest.xml ファイルに追加します。このクラスはインストルメント クラスタの表示を制御し、(オプションで)Navigation State API のデータをレンダリングできます。
  2. onCreate() の間に、このサービスを使用してレンダリング ハードウェアとの通信を初期化します。次のようなオプションがあります。
    • インストルメント クラスタに使用するセカンダリ ディスプレイを決定する。
    • 仮想ディスプレイを作成し、インストルメント クラスタアプリがそこに画像をレンダリングしてから(H.264 などの動画ストリーミング形式を使用して)外部ユニットに伝達できるようにする。
  3. 上のディスプレイの準備ができたら、このサービスは、InstrumentClusterRenderingService#setClusterActivityLaunchOptions() を呼び出して、インストルメント クラスタでのアクティビティの表示に使用すべき正確な ActivityOptions を定義する必要があります。次のパラメータを使用します。
    • category. ClusterRenderingService
    • ActivityOptions. インストルメント クラスタでアクティビティを起動するために使用できる ActivityOptions インスタンス。たとえば、AOSP でインストルメント クラスタの実装サンプルでは、次のようになります。
      getService().setClusterActivityLaunchOptions(
        CATEGORY_NAVIGATION,
        ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
      
  4. インストルメント クラスタがアクティビティを表示する準備ができたら、このサービスは InstrumentClusterRenderingService#setClusterActivityState() を呼び出す必要があります。次のパラメータを使用します。
    • category ClusterRenderingService
    • state ClusterRenderingService で生成されたバンドル。次の情報を提供してください。
      • visible 表示可能であり、コンテンツを表示する準備ができているとして、インストルメント クラスタを指定します。
      • unobscuredBounds コンテンツを安全に表示できるインストルメント クラスタ内の領域を定義する長方形。たとえば、ダイヤルやゲージによって覆われている領域など。
  5. Service#dump() メソッドをオーバーライドして、デバッグに役立つステータス情報をレポートします(詳細については、dumpsys をご覧ください)。

InstrumentClusterRenderingService の実装のサンプル

次の例は、InstrumentClusterRenderingService 実装の概要を示しています。これにより、リモートの物理ディスプレイにインストルメント クラスタ コンテンツを表示する VirtualDisplay が作成されます。

または、HU に接続された物理的なセカンダリ ディスプレイを使用できることが明らかな場合は、このコードでその displayId を渡すこともできます。

/**
* Sample {@link InstrumentClusterRenderingService} implementation
*/
public class SampleClusterServiceImpl extends InstrumentClusterRenderingService {
   // Used to retrieve or create displays
   private final DisplayManager mDisplayManager;
   // Unique identifier for the display to be used for instrument
   // cluster
   private final String mUniqueId = UUID.randomUUID().toString();
   // Format of the instrument cluster display
   private static final int DISPLAY_WIDTH = 1280;
   private static final int DISPLAY_HEIGHT = 720;
   private static final int DISPLAY_DPI = 320;
   // Area not covered by instruments
   private static final int DISPLAY_UNOBSCURED_LEFT = 40;
   private static final int DISPLAY_UNOBSCURED_TOP = 0;
   private static final int DISPLAY_UNOBSCURED_RIGHT = 1200;
   private static final int DISPLAY_UNOBSCURED_BOTTOM = 680;
   @Override
   public void onCreate() {
      super.onCreate();
      // Create a virtual display to render instrument cluster activities on
      mDisplayManager = getSystemService(DisplayManager.class);
      VirtualDisplay display = mDisplayManager.createVirtualDisplay(
          mUniqueId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_DPI, null,
          0 /* flags */, null, null);
      // Do any additional initialization (e.g.: start a video stream
      // based on this virtual display to present activities on a remote
      // display).
      onDisplayReady(display.getDisplay());
}
private void onDisplayReady(Display display) {
    // Report activity options that should be used to launch activities on
    // the instrument cluster.
    String category = CarInstrumentClusterManager.CATEGORY_NAVIGATION;
    ActionOptions options = ActivityOptions.makeBasic()
        .setLaunchDisplayId(display.getDisplayId());
    setClusterActivityOptions(category, options);
    // Report instrument cluster state.
    Rect unobscuredBounds = new Rect(DISPLAY_UNOBSCURED_LEFT,
        DISPLAY_UNOBSCURED_TOP, DISPLAY_UNOBSCURED_RIGHT,
        DISPLAY_UNOBSCURED_BOTTOM);
    boolean visible = true;
    ClusterActivityState state = ClusterActivityState.create(visible,
       unobscuredBounds);
    setClusterActivityState(category, options);
  }
}

CarAppFocusManager API を使用する

CarAppFocusManager API には getAppTypeOwner() という名前のメソッドが用意されており、OEM が作成したクラスタ サービスは常にどのナビゲーション アプリに操作フォーカスがあるかを認識できます。OEM は、既存の CarAppFocusManager#addFocusListener() メソッドを使用し、getAppTypeOwner() を使用して、どのアプリにフォーカスがあるのかを確認できます。この情報により、OEM は次のことを行えます。

  • クラスタに表示されるアクティビティを、フォーカスを保持しているナビゲーション アプリによって提供されるクラスタ アクティビティに切り替えます。
  • フォーカスのあるナビゲーション アプリにクラスタ アクティビティがあるかどうかを検出できます。フォーカスされているナビゲーション アプリにクラスタ アクティビティがない場合(またはそのようなアクティビティが無効になっている場合)、OEM はこのシグナルを車の DIM に送信し、クラスタのナビゲーション ファセットをすべてスキップできます。

CarAppFocusManager を使用して、アクティブ ナビゲーションや音声コマンドなど、現在のアプリのフォーカスを設定してリッスンします。通常、このようなアプリのインスタンスは、システム内で 1 つだけアクティブに実行(またはフォーカス)されています。

アプリのフォーカス変更をリッスンするには、CarAppFocusManager#addFocusListener(..) メソッドを使用します。

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
mAppFocusManager.addFocusListener(this, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

...

public void onAppFocusChanged(int appType, boolean active) {
    // Use the CarAppFocusManager#getAppTypeOwner(appType) method call
    // to retrieve a list of active package names
}

CarAppFocusManager#getAppTypeOwner(..) メソッドを使用して、フォーカスされている特定のアプリタイプの現在のオーナーのパッケージ名を取得します。現在のオーナーが android:sharedUserId 機能を使用している場合、このメソッドは複数のパッケージ名を返すことがあります。

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
List<String> focusOwnerPackageNames = mAppFocusManager.getAppTypeOwner(
              CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

if (focusOwnerPackageNames == null || focusOwnerPackageNames.isEmpty()) {
        // No Navigation app has focus
        // OEM may choose to show their default cluster view
} else {
       // focusOwnerPackageNames
       // Use the PackageManager to retrieve the cluster activity for the package(s)
       // returned in focusOwnerPackageNames
}

...

付録: サンプルアプリを使用する

AOSP には、Navigation State API を実装するサンプル アプリが用意されています。

このサンプルアプリを実行するには:

  1. サポートされている HU で Android Auto のビルドとフラッシュを行います。デバイス固有の Android ビルドおよびフラッシュ手順を実施してください。手順については、評価ボードの使用をご覧ください。
  2. 物理的なセカンダリ ディスプレイを HU に接続する(サポートされている場合)か、仮想セカンダリ HU をオンにします。
    1. 設定アプリで [デベロッパー モード] を選択します。
    2. [設定] > [システム] > [詳細設定] > [開発者向けオプション] > [2 次画面シミュレート] の順に移動します。
  3. HU を再起動します。
  4. KitchenSink アプリを起動するには:
    1. ドロワーを開きます。
    2. [Inst. Cluster] に移動します。
    3. [START METADATA] をクリックします。

KitchinkSink が、操作フォーカスをリクエストします。これにより、インストルメント クラスタにモックアップ ユーザー インターフェースを表示する指示が DirectRenderingCluster サービスに送られます。