Interfejsu Multi-Display Communications API może używać w Komunikacja z tą samą aplikacją (ta sama nazwa pakietu) w innym systemie AAOS w strefie podróży w samochodzie. Na tej stronie dowiesz się, jak zintegrować interfejs API. Aby się uczyć możesz też zobaczyć CarOccupantZoneManager.OccupantZoneInfo,
Strefa użytkowników
Pojęcie strefy zajętości przypisuje użytkownika do zestawu wyświetlaczy. Każdy w strefie osób jest wyświetlany typ DISPLAY_TYPE_MAIN. Strefa dla gości może też mieć dodatkowe ekrany, na przykład wyświetlacz klastra. Do każdej strefy jest przypisany użytkownik Androida. Każdy użytkownik ma własne konto i aplikacje.
Konfiguracja sprzętowa
Comms API obsługuje tylko jeden układ SOC. W modelu z pojedynczym układem SOC strefy i użytkownicy korzystają z tego samego układu SOC. Interfejs Comms API składa się z 3 komponentów:
Power Management API pozwala klientowi zarządzać w strefach użytkowników.
Discovery API umożliwia klientowi monitorowanie stanów innych osób w samochodzie oraz do monitorowania klientów równorzędnych w tych strefach. Używaj Discovery API przed użyciem Connection API.
Interfejs Connection API pozwala klientowi nawiązać połączenie z klientem równorzędnym w do kolejnej strefy zajętości, aby wysłać ładunek do klienta równorzędnego.
Do połączenia wymagane są interfejsy Discovery API i Connection API. Moc API do zarządzania jest opcjonalny.
Interfejs Comms API nie obsługuje komunikacji między różnymi aplikacjami. Zamiast tego: służy tylko do komunikacji między aplikacjami o tej samej nazwie pakietu i używany tylko do komunikacji między różnymi widocznymi użytkownikami.
Przewodnik po integracji
Implementowanie usługi AbstractReceiverService
Aby otrzymać Payload
, aplikacja odbierająca MUSI obsługiwać metody abstrakcyjne
zdefiniowane w: AbstractReceiverService
. Na przykład:
public class MyReceiverService extends AbstractReceiverService {
@Override
public void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone) {
}
@Override
public void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
@NonNull Payload payload) {
}
}
Funkcja onConnectionInitiated()
jest wywoływana, gdy klient nadawcy zażąda żądania
z tym klientem odbierającym. Jeśli konieczne jest potwierdzenie przez użytkownika,
połączenia, MyReceiverService
może zastąpić tę metodę, aby uruchomić
działania związane z uprawnieniami i wywoływanie w oparciu o acceptConnection()
lub rejectConnection()
na temat wyniku. W przeciwnym razie MyReceiverService
może zadzwonić po prostu
acceptConnection()
.
onPayloadReceived()is invoked when
MyReceiverServicehas received a
Payloadfrom the sender client.
MyReceiverService` może zastąpić to pole
:
- Przekieruj
Payload
do odpowiednich punktów końcowych odbiorcy(jeśli występują). Do pobierz zarejestrowane punkty końcowe odbiorcy, wywołajgetAllReceiverEndpoints()
. Do przekierowaniePayload
do danego punktu końcowego odbiorcy, wywołanieforwardPayload()
LUB
- Zapisz w pamięci podręcznej
Payload
i wysyłaj go, gdy oczekiwany punkt końcowy odbiorcy będzie zarejestrowanych, w przypadku których domenaMyReceiverService
jest powiadamiana przezonReceiverRegistered()
Deklarowanie usługi AbstractReceiverService
Aplikacja odbierająca MUSI zadeklarować zaimplementowany interfejs AbstractReceiverService
w swoich
plik manifestu, dodaj filtr intencji z działaniem
android.car.intent.action.RECEIVER_SERVICE
dla tej usługi i wymagają
Uprawnienie 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>
Uprawnienia android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE
gwarantuje, że tylko platforma może powiązać z tą usługą. Jeśli ta usługa
nie wymaga tych uprawnień, może je powiązać inna aplikacja
i wyślij do niej Payload
.
Zadeklaruj uprawnienia
Aplikacja kliencka MUSI zadeklarować uprawnienia w pliku manifestu.
<!-- 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"/>
Każde z trzech powyższych uprawnień to uprawnienia uprzywilejowane, które MUSZĄ być
wstępnie przyznane przez pliki z listy dozwolonych. Oto lista dozwolonych plików:
Aplikacja 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>
Pobieranie menedżerów samochodu
Aby używać interfejsu API, aplikacja kliencka MUSI zarejestrować CarServiceLifecycleListener
w
wyświetlić powiązanych menedżerów samochodu:
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);
(Nadawca) Discover
Przed połączeniem się z klientem odbierającym klient wysyłający POWINIEN wykryć
klienta odbierającego, rejestrując 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);
}
Zanim poprosisz o połączenie z odbiorcą, nadawca POWINIEN upewnić się, że wszystkie ustawione są flagi strefy docelowej i aplikacji odbierającej. W przeciwnym razie . Na przykład:
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;
}
Zalecamy, aby nadawca prosił o połączenie z odbiorcą tylko wtedy, gdy wszystkie flagi odbiorcy. Istnieją jednak wyjątki:
FLAG_OCCUPANT_ZONE_CONNECTION_READY
iFLAG_CLIENT_INSTALLED
to minimalne wymagania dotyczące nawiązywania połączenia.Jeśli aplikacja odbierająca musi wyświetlać interfejs, aby uzyskać zgodę użytkownika połączenie,
FLAG_OCCUPANT_ZONE_POWER_ON
iFLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
stają się dodatkowymi wymaganiami. Dla lepsze wrażenia użytkowników,FLAG_CLIENT_RUNNING
i Zalecane są równieżFLAG_CLIENT_IN_FOREGROUND
. W przeciwnym razie użytkownik może dziwić się.Aplikacja
FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
nie jest obecnie zaimplementowana (Android 15). Aplikacja kliencka może go po prostu zignorować.Obecnie (Android 15) interfejs Comms API obsługuje tylko wielu użytkowników instancję Androida, dzięki czemu aplikacje równorzędne mogą mieć ten sam długi kod wersji (
FLAG_CLIENT_SAME_LONG_VERSION
) i podpis (FLAG_CLIENT_SAME_SIGNATURE
). W efekcie aplikacje nie muszą sprawdzać, czy zgadzają się dwie wartości.
Dla wygody użytkowników klient nadawcy może wyświetlać interfejs, jeśli flaga nie jest
ustawiony. Jeśli na przykład FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED
nie jest ustawiony, nadawca
może wyświetlać tost lub okno z prośbą o odblokowanie ekranu
w strefie odbiornika.
Gdy nadawca nie musi już wykrywać odbiorców (na przykład znajduje wszystkich odbiorców i nawiązane połączenia lub przestaje być aktywny), CAN i zatrzymać odkrywanie.
if (mRemoteDeviceManager != null) {
mRemoteDeviceManager.unregisterStateCallback();
}
Zatrzymanie wykrywania nie ma wpływu na istniejące połączenia. Nadawca może
kontynuuj wysyłanie danych Payload
do połączonych odbiorników.
(Nadawca) Prośba o połączenie
Gdy ustawione są wszystkie flagi odbiorcy, nadawca może poprosić o połączenie do odbiorcy:
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);
}
(Usługa odbiornika) Zaakceptuj połączenie
Gdy nadawca poprosi o połączenie z odbiorcą,
Adres AbstractReceiverService
w aplikacji odbiornika będzie powiązany z usługą samochodową,
i AbstractReceiverService.onConnectionInitiated()
. Jako
omówione w artykule Wysyłanie żądania(nadawca),
onConnectionInitiated()
to metoda abstrakcyjna i MUSI być zaimplementowana przez
aplikacji klienckiej.
Gdy odbiorca zaakceptuje żądanie połączenia,
Zostanie wywołana funkcja ConnectionRequestCallback.onConnected()
, a następnie połączenie
(Nadawca) Wyślij ładunek
Po nawiązaniu połączenia nadawca może wysłać wiadomość Payload
do
odbiorca:
if (mOccupantConnectionManager != null) {
Payload payload = ...;
try {
mOccupantConnectionManager.sendPayload(receiverZone, payload);
} catch (CarOccupantConnectionManager.PayloadTransferException e) {
Log.e(TAG, "Failed to send Payload to " + receiverZone);
}
}
Nadawca może umieścić obiekt Binder
lub tablicę bajtów w obiekcie Payload
. Jeśli
nadawca musi wysyłać inne typy danych, MUSI zserializować dane do bajta
, użyj tablicy bajtów do utworzenia obiektu Payload
i wyślij
Payload
Następnie klient odbierający pobiera tablicę bajtów z odebranego
Payload
, i przekształca tablicę bajtów w oczekiwany obiekt danych.
Jeśli na przykład nadawca chce wysłać ciąg znaków hello
do odbiorcy
punktu końcowego o identyfikatorze FragmentB
, może użyć buforów Proto, aby określić typ danych
podobny do tego:
message MyData {
required string receiver_endpoint_id = 1;
required string data = 2;
}
Rysunek 1 przedstawia proces Payload
:
(Usługa odbiorcy) Odbieranie i wysyłanie ładunku
Gdy aplikacja odbierająca otrzyma Payload
,
Zostanie wywołana funkcja AbstractReceiverService.onPayloadReceived()
. Jak wyjaśniono w
Wyślij ładunek, onPayloadReceived()
to
to metoda abstrakcyjna i MUSI być wdrażana przez aplikację kliencką. W tej metodzie
klient MOŻE przekazać Payload
do odpowiednich punktów końcowych odbiorcy lub
zapisz w pamięci podręcznej Payload
, a następnie wyślij go, gdy oczekiwany punkt końcowy odbiorcy będzie
zarejestrowano.
(Punkt końcowy odbiorcy) Zarejestruj i wyrejestruj
Aplikacja odbierająca POWINNA wywołać metodę registerReceiver()
, by zarejestrować odbiorcę
i punktów końcowych. Typowym przypadkiem użycia jest to, że fragment kodu musi odbierać element Payload
, więc
rejestruje punkt końcowy odbiorcy:
private final PayloadCallback mPayloadCallback = (senderZone, payload) -> {
…
};
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.registerReceiver("FragmentB",
getActivity().getMainExecutor(), mPayloadCallback);
}
Gdy AbstractReceiverService
w kliencie odbierającym wyśle
Payload
do punktu końcowego odbiorcy, powiązane PayloadCallback
będzie
.
Aplikacja kliencka może rejestrować wiele punktów końcowych odbiorcy, o ile ich
Elementy typu receiverEndpointId
są unikalne w poszczególnych aplikacjach klienckich. receiverEndpointId
zostaną użyte przez aplikację AbstractReceiverService
do określenia, który odbiornik
punkty końcowe, do których ma być wysyłane ładunek. Na przykład:
- Nadawca określa
receiver_endpoint_id:FragmentB
wPayload
. Kiedy odbieranie komunikatówPayload
,AbstractReceiverService
w połączeniach odbierającychforwardPayload("FragmentB", payload)
w celu wysłania ładunku doFragmentB
- Nadawca określa
data_type:VOLUME_CONTROL
wPayload
. Kiedy odbiera wiadomośćPayload
,AbstractReceiverService
w odbiorniku wie że ten typPayload
powinien zostać wysłany doFragmentB
, więc wywołujeforwardPayload("FragmentB", payload)
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.unregisterReceiver("FragmentB");
}
(Nadawca) Zakończ połączenie
Gdy nadawca nie będzie już musiał wysyłać wiadomości Payload
do odbiorcy (na przykład
stanie się nieaktywne), POWINNA SIĘ zakończyć połączenie.
if (mOccupantConnectionManager != null) {
mOccupantConnectionManager.disconnect(receiverZone);
}
Po rozłączeniu nadawca nie będzie mógł wysyłać wiadomości Payload
do odbiorcy.
Przepływ połączenia
Przepływ połączeń został przedstawiony na Rys. 2.
Rozwiązywanie problemów
Sprawdzanie dzienników
Aby sprawdzić odpowiednie dzienniki:
Uruchom to polecenie, aby rozpocząć rejestrowanie:
adb shell setprop log.tag.CarRemoteDeviceService VERBOSE && adb shell setprop log.tag.CarOccupantConnectionService VERBOSE && adb logcat -s "AbstractReceiverService","CarOccupantConnectionManager","CarRemoteDeviceManager","CarRemoteDeviceService","CarOccupantConnectionService"
Aby skopiować stan wewnętrzny
CarRemoteDeviceService
iCarOccupantConnectionService
:adb shell dumpsys car_service --services CarRemoteDeviceService && adb shell dumpsys car_service --services CarOccupantConnectionService
Null CarRemoteDeviceManager i CarOccupantConnectionManager
Sprawdź te potencjalne główne przyczyny:
Usługa samochodowa uległa awarii. Jak widać na przykładzie, 2 menedżerowie celowo resetuje się do ustawienia
null
w przypadku wypadku w serwisie samochodowym. Gdy warsztat samochodowy po zrestartowaniu, oba menedżery mają niepuste wartości.CarRemoteDeviceService
alboCarOccupantConnectionService
nie są . Aby sprawdzić, czy któraś z nich jest włączona, uruchom polecenie:adb shell dumpsys car_service --services CarFeatureController
Odszukaj
mDefaultEnabledFeaturesFromConfig
, który powinien zawieraćcar_remote_device_service
icar_occupant_connection_service
. Dla: przykład: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]
Domyślnie te 2 usługi są wyłączone. Gdy urządzenie obsługuje na wiele wyświetlaczy, MUSISZ nałożyć ten plik konfiguracji. Możesz włączyć te 2 usługi w pliku konfiguracji:
// 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>
Wyjątek podczas wywoływania interfejsu API
Jeśli aplikacja kliencka nie używa interfejsu API w oczekiwany sposób, może wystąpić wyjątek. W takim przypadku aplikacja kliencka może sprawdzić wiadomość w wyjątku oraz stosu awarii, aby rozwiązać problem. Przykłady nadużyć interfejsu API:
registerStateCallback()
Ten klient zarejestrował jużStateCallback
.unregisterStateCallback()
Nie zarejestrowano przez to:StateCallback
CarRemoteDeviceManager
instancję.registerReceiver()
receiverEndpointId
jest już zarejestrowany.unregisterReceiver()
receiverEndpointId
nie jest zarejestrowany.requestConnection()
Oczekujące lub nawiązane połączenie już istnieje.cancelConnection()
Brak oczekujących połączeń do anulowania.sendPayload()
Brak nawiązanego połączenia.disconnect()
Brak nawiązanego połączenia.
Klient1 może wysłać ładunek do klienta 2, ale nie na odwrót
Połączenie jest zamierzone. Aby nawiązać połączenie dwukierunkowe, zarówno
client1
i client2
MUSZĄ poprosić o połączenie, a potem
uzyskać zgodę.