Multi-Display Communications API は、システム特権アプリでの使用が可能です。 AAOS が別のアプリ(同じパッケージ名)で実行されている同じアプリと通信 車内の乗員ゾーンですこのページでは、API を統合する方法について説明します。学習内容 さらに、 CarOccupantZoneManager.OccupantZoneInfo。
乗員ゾーン
乗員ゾーンという概念では、ユーザーを一連のディスプレイにマッピングします。各 乗員ゾーンにはディスプレイが DISPLAY_TYPE_MAIN。 乗員ゾーンには、クラスタ ディスプレイなどの追加ディスプレイも含まれる場合があります。 各乗員ゾーンには Android ユーザーが割り当てられます。各ユーザーは自分のアカウントを持つ できます。
ハードウェア構成
Comms API がサポートする SoC は 1 つのみです。単一 SoC モデルでは、乗員はすべて ゾーンとユーザーは同じ SoC で実行されます。Comms API は、次の 3 つのコンポーネントで構成されています。
Power Management API: クライアントがデバイスの電力 エリアに表示されます。
Discovery API により、クライアントは他の乗員の状態を監視できる 乗員ゾーン内のピア クライアントを監視できます。使用 Discovery API の使用を検討してください。
Connection API を使用すると、クライアントが ペイロードをピア クライアントに送信できます。
接続には Discovery API と Connection API が必要です。パワー Management API は省略可能です。
Comms API は、異なるアプリ間の通信をサポートしていません。代わりに 同じパッケージ名を持つアプリ間の通信専用に設計されている 表示される異なるユーザー間の通信にのみ使用されます。
統合ガイド
AbstractReceiverService を実装する
Payload
を受信するには、レシーバアプリは抽象メソッドを実装しなければなりません。
AbstractReceiverService
で定義されています。例:
public class MyReceiverService extends AbstractReceiverService {
@Override
public void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone) {
}
@Override
public void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
@NonNull Payload payload) {
}
}
onConnectionInitiated()
は、送信側のクライアントが
必要があります。認証と認可についてユーザーの確認が必要な場合は、
MyReceiverService
は、このメソッドをオーバーライドして
権限アクティビティに基づいて、acceptConnection()
または rejectConnection()
を呼び出します。
表示されます。それ以外の場合、MyReceiverService
は単に次の呼び出しを行います。
acceptConnection()
。
onPayloadReceived()is invoked when
MyReceiverServicehas received a
Payloadfrom the sender client.
MyReceiverService` はこれをオーバーライドできます
メソッドを次のように作成します。
Payload
を対応するレシーバ エンドポイントに転送します(存在する場合)。宛先 登録済みのレシーバ エンドポイントを取得するには、getAllReceiverEndpoints()
を呼び出します。宛先Payload
を特定のレシーバー エンドポイントに転送するには、forwardPayload()
を呼び出します。
または
Payload
をキャッシュに保存し、想定されるレシーバ エンドポイントに応じて送信 登録され、そのMyReceiverService
は以下を通じて通知されます。onReceiverRegistered()
AbstractReceiverService を宣言する
レシーバアプリは、実装された AbstractReceiverService
を
アクションを指定してインテント フィルタを追加し、
このサービスには android.car.intent.action.RECEIVER_SERVICE
が必要です。また、
android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
権限:
<service android:name=".MyReceiverService"
android:permission="android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.car.intent.action.RECEIVER_SERVICE" />
</intent-filter>
</service>
android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
権限
フレームワークのみがこのサービスにバインドできることを保証します。このサービスが
権限を必要としないため、別のアプリがこれにバインドできる可能性があります
Payload
を直接送信できます。
権限を宣言する
クライアント アプリは、マニフェスト ファイルで権限を宣言しなければなりません。
<!-- This permission is needed for connection API -->
<uses-permission android:name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<!-- This permission is needed for discovery API -->
<uses-permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
<!-- This permission is needed if the client app calls CarRemoteDeviceManager#setOccupantZonePower() -->
<uses-permission android:name="android.car.permission.CAR_POWER"/>
上記の 3 つの権限のそれぞれは特権であり、付与されなければなりません。
付与されています。たとえば、ここに示す許可リストファイルは
MultiDisplayTest
アプリ:
// packages/services/Car/data/etc/com.google.android.car.multidisplaytest.xml
<permissions>
<privapp-permissions package="com.google.android.car.multidisplaytest">
… …
<permission name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"/>
<permission name="android.car.permission.MANAGE_REMOTE_DEVICE"/>
<permission name="android.car.permission.CAR_POWER"/>
</privapp-permissions>
</permissions>
車のマネージャーを取得する
API を使用するには、クライアント アプリは CarServiceLifecycleListener
を登録する必要があります。
関連する自動車マネージャーを取得します。
private CarRemoteDeviceManager mRemoteDeviceManager;
private CarOccupantConnectionManager mOccupantConnectionManager;
private final Car.CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
if (!ready) {
Log.w(TAG, "Car service crashed");
mRemoteDeviceManager = null;
mOccupantConnectionManager = null;
return;
}
mRemoteDeviceManager = car.getCarManager(CarRemoteDeviceManager.class);
mOccupantConnectionManager = car.getCarManager(CarOccupantConnectionManager.class);
};
Car.createCar(getContext(), /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
mCarServiceLifecycleListener);
(送信者)Discover
送信側クライアントは、受信側クライアントに接続する前に、
CarRemoteDeviceManager.StateCallback
を登録してレシーバ クライアントを作成します。
// The maps are accessed by the main thread only, so there is no multi-thread issue.
private final ArrayMap<OccupantZoneInfo, Integer> mOccupantZoneStateMap = new ArrayMap<>();
private final ArrayMap<OccupantZoneInfo, Integer> mAppStateMap = new ArrayMap<>();
private final StateCallback mStateCallback = new StateCallback() {
@Override
public void onOccupantZoneStateChanged(
@androidx.annotation.NonNull OccupantZoneInfo occupantZone,
int occupantZoneStates) {
mOccupantZoneStateMap.put(occupantZone, occupantZoneStates);
}
@Override
public void onAppStateChanged(
@androidx.annotation.NonNull OccupantZoneInfo occupantZone,
int appStates) {
mAppStateMap.put(occupantZone, appStates);
}
};
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.registerStateCallback(getActivity().getMainExecutor(),
mStateCallback);
}
受信側への接続をリクエストする前に、送信側は受信側の接続に レシーバーの乗員ゾーンとレシーバーアプリのフラグが設定されます。それ以外の場合は エラーが発生する場合があります。例:
private boolean canRequestConnectionToReceiver(OccupantZoneInfo receiverZone) {
Integer zoneState = mOccupantZoneStateMap.get(receiverZone);
if ((zoneState == null) || (zoneState.intValue() & (FLAG_OCCUPANT_ZONE_POWER_ON
// FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED is not implemented yet. Right now
// just ignore this flag.
// | FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
| FLAG_OCCUPANT_ZONE_CONNECTION_READY)) == 0) {
return false;
}
Integer appState = mAppStateMap.get(receiverZone);
if ((appState == null) ||
(appState.intValue() & (FLAG_CLIENT_INSTALLED
| FLAG_CLIENT_SAME_LONG_VERSION | FLAG_CLIENT_SAME_SIGNATURE
| FLAG_CLIENT_RUNNING | FLAG_CLIENT_IN_FOREGROUND)) == 0) {
return false;
}
return true;
}
接続のリクエストが送信されるのは、接続が確立された 設定されます。ただし、次のような例外があります。
FLAG_OCCUPANT_ZONE_CONNECTION_READY
とFLAG_CLIENT_INSTALLED
: 接続の確立に必要な最小要件を確認します受信側のアプリで、承認を受けるために UI を表示する必要がある場合、 接続、
FLAG_OCCUPANT_ZONE_POWER_ON
、FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
が追加要件となります。1 つの ユーザー エクスペリエンスの向上、FLAG_CLIENT_RUNNING
、FLAG_CLIENT_IN_FOREGROUND
も推奨されています。指定しない場合、ユーザーが 驚かれるかもしれません。現時点では(Android 15)、
FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
は実装されていません。 クライアント アプリは無視できます。現在(Android 15)は、Comms API は、同じ 類似アプリが同じ長いバージョン コードを持つことができるように、Android インスタンスを作成 (
FLAG_CLIENT_SAME_LONG_VERSION
)と署名 (FLAG_CLIENT_SAME_SIGNATURE
)。そのため、アプリが 2 つの値が一致します
ユーザー エクスペリエンスを向上させるため、送信者のクライアントはフラグが付いていない場合に UI を表示できます。
あります。たとえば、FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
が設定されていない場合、送信者は
トーストやダイアログを表示して、デバイスの画面のロックを解除するようユーザーに促すことができます。
受信機の乗員ゾーンです
送信者が受信者を検出する必要がなくなった場合(たとえば、 すべてのレシーバと確立された接続を見つけるか、非アクティブになる)を 検出を停止します。
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.unregisterStateCallback();
}
検出が停止しても、既存の接続に影響はありません。送信者は
接続されたレシーバーに Payload
を引き続き送信します。
(送信者)接続をリクエストする
受信側のすべてのフラグが設定されると、送信側は接続をリクエストできます 送信します。
private final ConnectionRequestCallback mRequestCallback = new ConnectionRequestCallback() {
@Override
public void onConnected(OccupantZoneInfo receiverZone) {
}
@Override
public void onFailed(OccupantZoneInfo receiverZone, int connectionError) {
}
@Override
public void onDisconnected(OccupantZoneInfo receiverZone) {
}
};
if (mOccupantConnectionManager != null && canRequestConnectionToReceiver(receiverZone)) {
mOccupantConnectionManager.requestConnection(receiverZone,
getActivity().getMainExecutor(), mRequestCallback);
}
(レシーバー サービス)接続を受け入れる
送信側が受信側への接続をリクエストすると、
レシーバアプリの AbstractReceiverService
は、車のサービスによってバインドされます。
そして AbstractReceiverService.onConnectionInitiated()
が呼び出されます。として
(送信者)接続の要求で説明されているとおりに、
onConnectionInitiated()
は抽象化されたメソッドであり、
ダウンロードします。
受信側が接続リクエストを受け入れると、送信側は
ConnectionRequestCallback.onConnected()
が呼び出され、接続が
確立されます
(送信者)ペイロードを送信する
接続が確立されると、送信者は Payload
を
receive:
if (mOccupantConnectionManager != null) {
Payload payload = ...;
try {
mOccupantConnectionManager.sendPayload(receiverZone, payload);
} catch (CarOccupantConnectionManager.PayloadTransferException e) {
Log.e(TAG, "Failed to send Payload to " + receiverZone);
}
}
送信側は、Binder
オブジェクトまたはバイト配列を Payload
に配置できます。もし
送信側が他のデータ型を送信する必要がある場合は、データをバイト値にシリアル化しなければなりません。
そのバイト配列を使用して Payload
オブジェクトを作成し、
Payload
。次に、受信側のクライアントは受信したバイト配列から
Payload
とし、バイト配列を想定されるデータ オブジェクトにシリアル化解除します。
たとえば、送信者が文字列 hello
を受信者に送信する場合などです。
そのエンドポイントの ID が FragmentB
の場合、プロトコル バッファを使用して
例:
message MyData {
required string receiver_endpoint_id = 1;
required string data = 2;
}
図 1 に Payload
フローを示します。
(Receiver サービス)ペイロードを受信して送信する
受信側のアプリが Payload
を受信すると、そのアプリが
AbstractReceiverService.onPayloadReceived()
が呼び出されます。詳しくは、
ペイロードを送信する。onPayloadReceived()
は
クライアント アプリで実装しなければなりません。この方法では、
クライアントが Payload
を対応する受信エンドポイントに転送できる。または
Payload
をキャッシュに保存し、想定されるレシーバ エンドポイントに達したら送信します。
あります。
(レシーバー エンドポイント)登録と登録解除
レシーバアプリは registerReceiver()
を呼び出してレシーバを登録すべきです。
提供します一般的なユースケースでは、Fragment が Payload
を受け取る必要があるため、
レシーバー エンドポイントを登録します。
private final PayloadCallback mPayloadCallback = (senderZone, payload) -> {
…
};
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.registerReceiver("FragmentB",
getActivity().getMainExecutor(), mPayloadCallback);
}
レシーバ クライアントの AbstractReceiverService
が
Payload
をレシーバ エンドポイントに送信すると、関連付けられている PayloadCallback
は次のようになります
呼び出すことができます。
クライアント アプリは、デバイスが 1 つである限り、複数のレシーバ エンドポイントを登録できます。
receiverEndpointId
はクライアント アプリ間で一意です。receiverEndpointId
AbstractReceiverService
がどのレシーバーを使用するかを決定するために使用されます。
ペイロードを送信するエンドポイントを指定できます。例:
- 送信者が
Payload
にreceiver_endpoint_id:FragmentB
を指定している。日時Payload
を受け取ると、レシーバーのAbstractReceiverService
が呼び出します。forwardPayload("FragmentB", payload)
を使用してペイロードをFragmentB
- 送信者が
Payload
にdata_type:VOLUME_CONTROL
を指定している。日時Payload
を受信した場合、受信側のAbstractReceiverService
は このタイプのPayload
はFragmentB
にディスパッチする必要があるため、forwardPayload("FragmentB", payload)
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.unregisterReceiver("FragmentB");
}
(送信者)接続の終端
送信者が受信者に Payload
を送信する必要がなくなったら(例:
非アクティブになった場合)、接続を終了すべきです。
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.disconnect(receiverZone);
}
接続が解除されると、送信者は受信者に Payload
を送信できなくなります。
接続フロー
接続フローを図 2 に示します。
トラブルシューティング
ログを確認する
対応するログを確認するには:
ロギングするには次のコマンドを実行します。
adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
CarRemoteDeviceService
の内部状態をダンプし、CarOccupantConnectionService
:adb shell dumpsys car_service --services CarRemoteDeviceService && adb shell dumpsys car_service --services CarOccupantConnectionService
CarRemoteDeviceManager と CarOccupantConnectionManager が null である
考えられる根本原因は次のとおりです。
カーサービスで問題が発生しました。前述したように、2 人のマネージャーは カーサービス障害が発生した場合に、意図的に
null
にリセットされるようになりました。車のサービス 再起動されると、2 つのマネージャーは非 null 値に設定されます。CarRemoteDeviceService
とCarOccupantConnectionService
のどちらも一致しない 有効にします。どちらか一方が有効になっているかどうかを確認するには、次のコマンドを実行します。adb shell dumpsys car_service --services CarFeatureController
mDefaultEnabledFeaturesFromConfig
を探します。car_remote_device_service
とcar_occupant_connection_service
。次に例を示します。mDefaultEnabledFeaturesFromConfig:[car_evs_service, car_navigation_service, car_occupant_connection_service, car_remote_device_service, car_telemetry_service, cluster_home_service, com.android.car.user.CarUserNoticeService, diagnostic, storage_monitoring, vehicle_map_service]
デフォルトでは、この 2 つのサービスは無効になっています。デバイスが この構成ファイルをオーバーレイする必要があります。有効にすることで これら 2 つのサービスを構成ファイルに記述します。
// packages/services/Car/service/res/values/config.xml <string-array translatable="false" name="config_allowed_optional_car_features"> <item>car_occupant_connection_service</item> <item>car_remote_device_service</item> … … </string-array>
API 呼び出し時の例外
クライアント アプリが API を意図したとおりに使用していない場合は、例外が発生する可能性があります。 この場合、クライアント アプリは例外のメッセージを確認し、 クラッシュ スタックを修正して、問題を解決します。API の不正使用の例は次のとおりです。
registerStateCallback()
このクライアントはすでにStateCallback
を登録しています。unregisterStateCallback()
StateCallback
は登録されたものではありませんCarRemoteDeviceManager
インスタンス。registerReceiver()
receiverEndpointId
はすでに登録されています。unregisterReceiver()
receiverEndpointId
は登録されていません。requestConnection()
保留中または確立済みの接続がすでに存在します。cancelConnection()
キャンセルできる保留中の接続はありません。sendPayload()
: 接続が確立されていません。disconnect()
: 接続が確立されていません。
Client1 は client2 にペイロードを送信できるが、その逆はできない
接続は一方向の設計です。双方向接続を確立するには、
client1
と client2
は相互に接続をリクエストしなければならず、
承認を得る必要があります。