Die Multi-Display Communications API kann von einer privilegierten System-App in AAOS, um mit derselben App (gleicher Paketname) zu kommunizieren, die in einem anderen Insassenbereich in einem Auto. Auf dieser Seite wird beschrieben, wie die API integriert wird. Weitere Informationen können Sie auch sehen, CarOccupantZoneManager.OccupantZoneInfo.
Insassenbereich
Mit dem Konzept der Insassenzone wird ein Nutzer einer Gruppe von Displays zugeordnet. Jedes Insassenbereich ein Display mit dem Typ DISPLAY_TYPE_MAIN ist. Eine Insassenzone kann auch zusätzliche Bildschirme haben, z. B. ein Cluster-Display. Jedem Insassenbereich ist ein Android-Nutzer zugewiesen. Jeder Nutzer hat ein eigenes Konto und Apps.
Hardwarekonfiguration
Die Comms API unterstützt nur ein einziges SoC. Bei einem einzelnen SoC-Modell sind alle Insassen Zonen und Nutzer werden auf demselben SoC ausgeführt. Die Comms API besteht aus drei Komponenten:
Mit der Power Management API kann der Client die Leistung des in den Insassenbereichen angezeigt.
Mit der Discovery API kann der Client den Status anderer Insassen überwachen. und Peer-Clients in diesen Insassenbereichen überwachen. Verwenden Sie bevor Sie die Connection API verwenden.
Mit der Connection API kann der Client eine Verbindung zu seinem Peer-Client herstellen, und eine Nutzlast an den Peer-Client zu senden.
Für die Verbindung sind die Discovery API und die Connection API erforderlich. Die Macht Management API ist optional.
Die Comms API unterstützt keine Kommunikation zwischen verschiedenen Apps. Stattdessen er ist nur für die Kommunikation zwischen Apps mit demselben Paketnamen vorgesehen. und wird nur für die Kommunikation zwischen verschiedenen sichtbaren Nutzern verwendet.
Integrationsleitfaden
AbstractReceiverService implementieren
Zum Empfangen von Payload
MÜSSEN die abstrakten Methoden in der Empfänger-App implementiert werden
definiert in AbstractReceiverService
. Beispiel:
public class MyReceiverService extends AbstractReceiverService {
@Override
public void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone) {
}
@Override
public void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
@NonNull Payload payload) {
}
}
onConnectionInitiated()
wird aufgerufen, wenn der Absenderclient ein
Verbindung zu diesem Empfängerclient herstellen. Ist eine Bestätigung des Nutzers erforderlich,
der Verbindung, kann MyReceiverService
diese Methode überschreiben, um ein
Berechtigungsaktivität und acceptConnection()
oder rejectConnection()
aufrufen
für das Ergebnis. Andernfalls kann MyReceiverService
einfach anrufen
acceptConnection()
.
onPayloadReceived()is invoked when
MyReceiverServicehas received a
Payloadfrom the sender client.
MyReceiverServicekann diese Änderung überschreiben
zu:
- Leite die
Payload
an die entsprechenden Empfängerendpunkte weiter, falls vorhanden. Bis Rufen SiegetAllReceiverEndpoints()
auf, um die registrierten Empfängerendpunkte abzurufen. BisPayload
an einen bestimmten Empfängerendpunkt weiterleiten,forwardPayload()
aufrufen
ODER
- Speichern Sie die
Payload
im Cache und senden Sie sie, wenn der erwartete Empfängerendpunkt registriert, über die dieMyReceiverService
benachrichtigt wird durchonReceiverRegistered()
AbstractReceiverService deklarieren
Die Empfänger-App MÜSSEN die implementierte AbstractReceiverService
in ihrer
Manifestdatei, Intent-Filter mit Aktion hinzufügen
android.car.intent.action.RECEIVER_SERVICE
für diesen Dienst und erfordern die
android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
-Berechtigung:
<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>
Die Berechtigung android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
stellt sicher, dass nur das Framework an diesen Dienst gebunden werden kann. Wenn dieser Dienst
keine Berechtigung benötigt, kann eine andere App möglicherweise
und senden Sie direkt Payload
an ihn.
Berechtigung deklarieren
Die Client-App MÜSSEN die Berechtigungen in ihrer Manifest-Datei deklarieren.
<!-- 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"/>
Jede der drei oben genannten Berechtigungen ist privilegierte Berechtigungen, die MÜSSEN
Zulassungslistendateien vorab gewährt werden. Hier sehen Sie z. B. die Datei mit der Zulassungsliste
MultiDisplayTest
App:
// 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>
Automanager abrufen
Um die API verwenden zu können, MUSS die Client-App eine CarServiceLifecycleListener
registrieren, um
die zugehörigen Automanager abrufen:
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 (Absender)
Vor der Verbindung mit dem Empfängerclient SOLLTE der Absenderclient ermitteln,
-Empfänger-Client durch Registrieren eines 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);
}
Vor dem Anfordern einer Verbindung zum Empfänger SOLLTE der Absender sicherstellen, Die Flags der Zone für die Belegung durch den Empfänger und der Empfänger-App sind festgelegt. Andernfalls können Fehler auftreten. Beispiel:
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;
}
Wir empfehlen, dass der Absender nur dann eine Verbindung zum Empfänger anfordert, wenn alle des Empfängers gesetzt sind. Es gibt jedoch Ausnahmen:
FLAG_OCCUPANT_ZONE_CONNECTION_READY
undFLAG_CLIENT_INSTALLED
sind die Mindestanforderungen zum Herstellen einer Verbindung.Wenn die Empfänger-App eine Benutzeroberfläche anzeigen muss, um die Verbindung,
FLAG_OCCUPANT_ZONE_POWER_ON
undFLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
zusätzliche Anforderungen. Für eine bessere Nutzererfahrung,FLAG_CLIENT_RUNNING
undFLAG_CLIENT_IN_FOREGROUND
werden auch empfohlen, sonst könnte der Nutzer seien Sie überrascht.Im Moment ist
FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
nicht implementiert (Android 15). Die Client-App kann ihn einfach ignorieren.Derzeit (Android 15) unterstützt die Comms API nur mehrere Nutzer auf demselben Android-Instanz, damit Peer-Apps denselben langen Versionscode haben können (
FLAG_CLIENT_SAME_LONG_VERSION
) und Signatur (FLAG_CLIENT_SAME_SIGNATURE
) Daher müssen Apps nicht verifizieren, dass das zwei Werte übereinstimmen.
Für eine bessere Nutzererfahrung KANN der Absender-Client eine UI anzeigen, wenn eine Markierung nicht
festgelegt. Beispiel: Wenn FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
nicht festgelegt ist,
einen Toast oder ein Dialogfeld anzeigen, das den Nutzer auffordert,
Insassenbereich des Empfängers.
Wenn der Absender die Empfänger nicht mehr ermitteln muss (z. B. wenn alle Empfänger und hergestellten Verbindungen findet oder inaktiv wird), CAN die Entdeckung zu stoppen.
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.unregisterStateCallback();
}
Wenn die Erkennung angehalten wird, sind bestehende Verbindungen nicht betroffen. Der Absender kann
weiterhin Payload
an die verbundenen Empfänger senden.
(Sender) Anfrageverbindung
Wenn alle Flags des Empfängers festgelegt sind, kann der Absender eine Verbindung anfordern an den Empfänger:
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);
}
(Empfängerdienst) Verbindung akzeptieren
Sobald der Sender eine Verbindung zum Empfänger anfordert,
AbstractReceiverService
in der Empfänger-App wird an den Autodienst gebunden,
und AbstractReceiverService.onConnectionInitiated()
werden aufgerufen. Als
im Artikel Anfrageverbindung(Sender)
onConnectionInitiated()
ist eine abstrahierte Methode und MUSS vom
Client-App.
Wenn der Empfänger die Verbindungsanfrage akzeptiert,
ConnectionRequestCallback.onConnected()
aufgerufen wird, dann wird die Verbindung
festgelegt ist.
(Sender) Nutzlast senden
Sobald die Verbindung hergestellt ist, kann der Absender Payload
an die
Empfänger:
if (mOccupantConnectionManager != null) {
Payload payload = ...;
try {
mOccupantConnectionManager.sendPayload(receiverZone, payload);
} catch (CarOccupantConnectionManager.PayloadTransferException e) {
Log.e(TAG, "Failed to send Payload to " + receiverZone);
}
}
Der Absender kann ein Binder
-Objekt oder ein Byte-Array in Payload
einfügen. Wenn die
andere Datentypen senden muss, MUSS er die Daten in einem Byte serialisieren
Array, verwenden Sie das Byte-Array, um ein Payload
-Objekt zu erstellen, und senden Sie den Parameter
Payload
Dann erhält der Empfängerclient das Byte-Array von der empfangenen
Payload
und deserialisiert das Byte-Array in das erwartete Datenobjekt.
Wenn der Absender beispielsweise den String hello
an den Empfänger senden möchte
Endpunkt mit der ID FragmentB
hat, kann mit Proto Buffers ein Datentyp definiert werden.
wie hier:
message MyData {
required string receiver_endpoint_id = 1;
required string data = 2;
}
In Abbildung 1 wird der Payload
-Ablauf veranschaulicht:
(Empfängerdienst) Nutzlast empfangen und versenden
Sobald die Empfänger-App die Payload
empfängt, wird
AbstractReceiverService.onPayloadReceived()
wird aufgerufen. Wie in den
Send the Payload, onPayloadReceived()
ist ein
abstrahierte Methode und MUSS von der Client-App implementiert werden. Bei dieser Methode
Der Client KANN Payload
an die entsprechenden Empfängerendpunkte weiterleiten.
Payload
im Cache speichern und sie senden, sobald der erwartete Empfängerendpunkt
registriert.
(Empfängerendpunkt) Registrieren und Registrierung aufheben
Die Empfänger-App SOLLTE registerReceiver()
aufrufen, um den Empfänger zu registrieren.
Endpunkten. Ein typischer Anwendungsfall ist, dass ein Fragment den Empfänger Payload
benötigt.
wird ein Empfängerendpunkt registriert:
private final PayloadCallback mPayloadCallback = (senderZone, payload) -> {
…
};
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.registerReceiver("FragmentB",
getActivity().getMainExecutor(), mPayloadCallback);
}
Sobald AbstractReceiverService
im Empfängerclient die Methode
Payload
an den Empfängerendpunkt, wird die zugehörige PayloadCallback
aufgerufen wird.
Die Client-App KANN mehrere Empfängerendpunkte registrieren,
receiverEndpointId
s sind innerhalb der Client-App eindeutig. Das receiverEndpointId
wird von AbstractReceiverService
verwendet, um zu entscheiden, welcher Empfänger
Endpunkt(en) zum Senden der Nutzlast. Beispiel:
- Der Absender gibt in
Payload
receiver_endpoint_id:FragmentB
an. Wann? beim Empfang vonPayload
, demAbstractReceiverService
des EmpfängersforwardPayload("FragmentB", payload)
zum Senden der Nutzlast anFragmentB
- Der Absender gibt in
Payload
data_type:VOLUME_CONTROL
an. Wann? beim Empfang derPayload
weiß derAbstractReceiverService
im Empfänger, dass dieser Typ vonPayload
anFragmentB
weitergeleitet werden soll. Daher ruft erforwardPayload("FragmentB", payload)
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.unregisterReceiver("FragmentB");
}
(Absender) Verbindung beenden
Wenn der Absender Payload
nicht mehr an den Empfänger senden muss, z. B.
wird er inaktiv), SOLLTE die Verbindung beendet werden.
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.disconnect(receiverZone);
}
Wenn die Verbindung getrennt ist, kann der Absender keine Payload
mehr an den Empfänger senden.
Verbindungsablauf
Ein Verbindungsablauf ist in Abbildung 2 dargestellt.
Fehlerbehebung
Logs prüfen
So überprüfen Sie die entsprechenden Logs:
Führen Sie für das Logging folgenden Befehl aus:
adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
Zum Dump des internen Status von
CarRemoteDeviceService
undCarOccupantConnectionService
:adb shell dumpsys car_service --services CarRemoteDeviceService && adb shell dumpsys car_service --services CarOccupantConnectionService
Null CarRemoteDeviceManager und CarOccupantConnectionManager
Sieh dir diese möglichen Ursachen an:
Die Autowerkstatt ist abgestürzt. Wie bereits gezeigt, sind die beiden Führungskräfte wurde absichtlich auf
null
zurückgesetzt, wenn der Autodienst abstürzt. Wenn der Fahrdienst neu gestartet wird, werden die beiden Manager auf Nicht-Null-Werte gesetzt.Entweder
CarRemoteDeviceService
oderCarOccupantConnectionService
ist nicht aktiviert. Führen Sie den folgenden Befehl aus, um festzustellen, ob eine der beiden Funktionen aktiviert ist:adb shell dumpsys car_service --services CarFeatureController
Suchen Sie nach
mDefaultEnabledFeaturesFromConfig
. Es sollte Folgendes enthalten:car_remote_device_service
undcar_occupant_connection_service
. Für Beispiel: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]
Diese beiden Dienste sind standardmäßig deaktiviert. Wenn ein Gerät Multi-Display-Anzeige verwenden, MÜSSEN Sie diese Konfigurationsdatei überlagern. Sie können die beiden Dienste in einer Konfigurationsdatei:
// 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>
Ausnahme beim API-Aufruf
Wenn die Client-App die API nicht wie vorgesehen verwendet, kann eine Ausnahme auftreten. In diesem Fall kann die Client-App die Meldung in der Ausnahme prüfen Crash-Stack, um das Problem zu beheben. Beispiele für einen Missbrauch der API:
registerStateCallback()
Dieser Client hat bereits einStateCallback
registriert.unregisterStateCallback()
Von diesem/dieser Person wurde kein(e)StateCallback
registriertCarRemoteDeviceManager
-Instanz.registerReceiver()
receiverEndpointId
ist bereits registriert.unregisterReceiver()
receiverEndpointId
ist nicht registriert.requestConnection()
Eine ausstehende oder bestehende Verbindung ist bereits vorhanden.cancelConnection()
Keine ausstehende Verbindung zum Abbrechen.sendPayload()
Keine Verbindung hergestellt.disconnect()
Keine Verbindung hergestellt.
Client1 kann Nutzlasten an client2 senden, aber nicht umgekehrt.
Die Verbindung ist designbedingt eine Möglichkeit. Zum Aufbau einer bidirektionalen Verbindung müssen beide
client1
und client2
MÜSSEN eine Verbindung miteinander anfordern und dann
Genehmigung einholen.