Multi-Display Communications API 可供具有特殊權限的系統應用程式使用 AAOS 用於與不同執行環境的同一應用程式 (相同的套件名稱) 通訊 乘客可能會看見車子的多個區域本頁說明如何整合 API。學習 您還能看到 CarocpantZoneManager.OccupantZoneInfo。
乘客區
「佔用區域」的概念會將使用者對應至一組螢幕。每項 佔用區域有 DISPLAY_TYPE_MAIN。 佔用區域也可能有其他螢幕,例如儀表板螢幕。 每個乘客區域都會獲派一名 Android 使用者。每位使用者都有自己的帳戶 和應用程式互動
硬體設定
Comms API 僅支援一個 SoC。在單一 SoC 模型中 可用區和使用者使用同一個 SoCComms API 由三個元件組成:
Power management API 可讓用戶端管理 會顯示於乘客區域。
Discovery API 可讓用戶端監控其他乘客的狀態 以及監控這些乘客區域的對等用戶端。使用 Discovery API,然後再使用 Connection 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"/>
上述三個權限個別都是特殊權限,
預先授予許可權限舉例來說,以下是
「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);
(寄件者) 探索
連線至接收端用戶端之前,傳送端用戶端應先發現
註冊 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
符合其他需求條件。換FLAG_CLIENT_RUNNING
和 也建議使用FLAG_CLIENT_IN_FOREGROUND
,否則使用者可能會 可能就出乎意料目前 (Android 15) 尚未實作
FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
。 用戶端應用程式則可直接忽略。Comms API 目前 (Android 15) 只支援多位使用者共用 Android 執行個體,讓對等互連應用程式可使用相同的長版代碼 (
FLAG_CLIENT_SAME_LONG_VERSION
) 和簽名 (FLAG_CLIENT_SAME_SIGNATURE
)。因此,應用程式不需驗證 兩個值一致
為了改善使用者體驗,傳送方用戶端「CAN」會在無法標記的情況下顯示使用者介面
設定。舉例來說,如果未設定 FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
,寄件者
可以顯示浮動式訊息或對話方塊,提示使用者解鎖
接收方針區域。
寄件者不再需要找到接收端時 (例如 尋找所有接收器和既有連線,或變得閒置時),則無法 停止探索
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.unregisterStateCallback();
}
如果停止探索,現有連線不會受到影響。傳送者可以
繼續將 Payload
傳送給已連結的接收器。
(傳送者) 要求連線
接收者的所有旗標均已設定後,傳送方 CAN 會要求連線 的指令後:
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()
阿斯
如 (Sender) 要求連線一節所述。
onConnectionInitiated()
是抽象方法,一定要由
用戶端應用程式。
接收者接受連線要求後,寄件者的
隨後,系統會叫用 ConnectionRequestCallback.onConnected()
,
因此狀態是已建立的。
(傳送者) 傳送酬載
建立連線後,傳送者即可傳送 Payload
給
接收方:
if (mOccupantConnectionManager != null) {
Payload payload = ...;
try {
mOccupantConnectionManager.sendPayload(receiverZone, payload);
} catch (CarOccupantConnectionManager.PayloadTransferException e) {
Log.e(TAG, "Failed to send Payload to " + receiverZone);
}
}
傳送者可以在 Payload
中加入 Binder
物件或位元組陣列。如果
傳送方需要傳送其他資料類型,則「必須」將資料序列化為位元組
陣列,請使用位元組陣列建構 Payload
物件,然後傳送
Payload
。接著,接收器用戶端會從
Payload
,並將位元組陣列反序列化為預期的資料物件。
例如,如果傳送方想要傳送字串 hello
給接收器
ID 為 FragmentB
的端點,可以使用 Proto 緩衝區定義資料類型
輸入:
message MyData {
required string receiver_endpoint_id = 1;
required string data = 2;
}
圖 1 說明 Payload
流程:
(接收端服務) 接收和調度酬載
接收端應用程式收到 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
將
呼叫。
用戶端應用程式 CAN 只要能夠註冊
用戶端應用程式中的 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
Null CarRemoteDeviceManager 和 CarOccupantConnectionManager
查看可能的根本原因:
車禍事故。如前所述,兩位經理 刻意將車輛服務當機時重設為
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]
根據預設,系統會停用這兩項服務。裝置支援時 ,您「必須」重疊這個設定檔。您可以啟用 設定檔中包含兩項服務:
// 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 可以將 Payload 傳送至 client2,但無法對用戶端傳送
連線方式為一種形式。如要建立雙向連線,
「client1
」和「client2
」必須要求建立連線,才能
獲得核准。