Instrument Cluster API(Android API)を使用することで、Google マップなどのナビゲーション アプリを車内のセカンダリ ディスプレイ(ハンドルの背後の計器パネル上など)に表示できます。このページでは、そのセカンダリ ディスプレイを制御するサービスを作成する方法、およびそのサービスを CarService
と統合して、ナビゲーション アプリにユーザー インターフェースを表示できるようにする方法について説明します。
用語
このページで使用される用語は次のとおりです。
CarManager
インスタンス。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 を更新する必要があります。
インストルメント クラスタをレンダリングすると、起動プロセス中に CarService
が ClusterRenderingService の InstrumentClusterRendererService
キーを読み取って、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 プラットフォームが提供する
CarService
とCarNavigationStatusManager
。詳細については、自動車と CAR_NAVIGATION_SERVICE をご覧ください。 - シアン OEM によって実装される
InstrumentClusterRendererService
。 - 紫 Google とサードパーティのデベロッパーが実装するナビゲーション アプリ。
- 緑
CarAppFocusManager
。詳細については、以下の CarAppFocusManager API の使用と CarAppFocusManager をご覧ください。
ナビゲーション状態情報フローは、次のようなシーケンスをたどります。
CarService
は、InstrumentClusterRenderingService
を初期化します。- 初期化中に、
InstrumentClusterRenderingService
は以下を使用してCarService
を更新します。- 不鮮明な境界などの、インストルメント クラスタの表示プロパティ(不鮮明な境界に関する詳細については、後ほどご確認ください)。
- インストルメント クラスタのディスプレイ内でアクティビティを起動するのに必要なアクティビティ オプション。詳しくは、ActivityOptions をご覧ください。
- ナビゲーション アプリ(Android Automotive 向け Google マップや、必要な権限を持つ地図アプリなど)が以下を行います。
- car-lib から Car クラスを使用して
CarAppFocusManager
を取得します。 - ターンバイターン方式ナビを開始する前に、
CarAppFocusManager.requestFocus()
を呼び出してCarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION
をappType
パラメータとして渡します。
- car-lib から Car クラスを使用して
CarAppFocusManager
がこのリクエストをCarService
に通知します。許可されている場合、CarService
はナビゲーション アプリのパッケージを検査し、カテゴリandroid.car.cluster.NAVIGATION
とマークされているアクティビティを探します。- 検出されると、ナビゲーション アプリは、
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 を実装する
サービスをビルドするには:
- ClusterRenderingService から拡張するクラスを作成し、対応するエントリを
AndroidManifest.xml
ファイルに追加します。このクラスはインストルメント クラスタの表示を制御し、(オプションで)Navigation State API のデータをレンダリングできます。 onCreate()
の間に、このサービスを使用してレンダリング ハードウェアとの通信を初期化します。次のようなオプションがあります。- インストルメント クラスタに使用するセカンダリ ディスプレイを決定する。
- 仮想ディスプレイを作成し、インストルメント クラスタアプリがそこに画像をレンダリングしてから(H.264 などの動画ストリーミング形式を使用して)外部ユニットに伝達できるようにする。
- 上のディスプレイの準備ができたら、このサービスは、
InstrumentClusterRenderingService#setClusterActivityLaunchOptions()
を呼び出して、インストルメント クラスタでのアクティビティの表示に使用すべき正確なActivityOptions
を定義する必要があります。次のパラメータを使用します。category.
ClusterRenderingService。ActivityOptions.
インストルメント クラスタでアクティビティを起動するために使用できるActivityOptions
インスタンス。たとえば、AOSP でインストルメント クラスタの実装サンプルでは、次のようになります。getService().setClusterActivityLaunchOptions( CATEGORY_NAVIGATION, ActivityOptions.makeBasic() .setLaunchDisplayId(displayId));
- インストルメント クラスタがアクティビティを表示する準備ができたら、このサービスは
InstrumentClusterRenderingService#setClusterActivityState()
を呼び出す必要があります。次のパラメータを使用します。category
ClusterRenderingService。state
ClusterRenderingService で生成されたバンドル。次の情報を提供してください。visible
表示可能であり、コンテンツを表示する準備ができているとして、インストルメント クラスタを指定します。unobscuredBounds
コンテンツを安全に表示できるインストルメント クラスタ内の領域を定義する長方形。たとえば、ダイヤルやゲージによって覆われている領域など。
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 を実装するサンプル アプリが用意されています。
このサンプルアプリを実行するには:
- サポートされている HU で Android Auto のビルドとフラッシュを行います。デバイス固有の Android ビルドおよびフラッシュ手順を実施してください。手順については、評価ボードの使用をご覧ください。
- 物理的なセカンダリ ディスプレイを HU に接続する(サポートされている場合)か、仮想セカンダリ HU をオンにします。
- 設定アプリで [デベロッパー モード] を選択します。
- [設定] > [システム] > [詳細設定] > [開発者向けオプション] > [2 次画面シミュレート] の順に移動します。
- HU を再起動します。
- KitchenSink アプリを起動するには:
- ドロワーを開きます。
- [Inst. Cluster] に移動します。
- [START METADATA] をクリックします。
KitchinkSink が、操作フォーカスをリクエストします。これにより、インストルメント クラスタにモックアップ ユーザー インターフェースを表示する指示が DirectRenderingCluster
サービスに送られます。