SDV Gateway auf IVI verwenden

Das SDV-Gateway (Software Defined Vehicle) in Infotainmentsystemen (In-Vehicle Infotainment, IVI) ermöglicht die Kommunikation zwischen IVI-Systemen und Remote-SDV-Diensten. Über das Gateway können OEM-Java-Apps und native Dienste wie VHAL mit SDV-Diensten interagieren. Das Gateway verwendet etablierte Kommunikationsmethoden wie Dienstregistrierung, ‑ermittlung und RPC.

Dieses Gateway erfüllt bestimmte Anforderungen des AAOS SDV-Projekts, z. B. die Aktivierung einer VHAL-Referenzimplementierung mit dem SDV-Datentunnel für Attributinformationen. Außerdem können Java- und Kotlin-Android-Apps auf IVI den SDV-Kommunikationsstack verwenden, sich als Dienste registrieren, andere SDV-Dienste finden und mit ihnen kommunizieren.

Informationen zu den Code-Speicherorten des SDV Gateway und zu Beispiel-SDV Gateway-Clients finden Sie unter Code-Speicherorte.

SDV Gateway-Integrationsmodelle

SDV-IVI-Kommunikation über das SDV-Gateway in der IVI-Architektur verwenden

Das SDV-Gateway interagiert mit Java-Apps, nativen Diensten, dem SDV-Comms-Stack und dem Fahrzeugnetzwerk. Abbildung 1 veranschaulicht diese Interaktionen:

SDV-Gateway-Interaktionen

Abbildung 1. SDV Gateway-Interaktionen.

In diesem Systemdiagramm gilt Folgendes:

  • Apps interagieren über die Clientdienste mit dem SDV-Gateway.
  • Das AAOS SDV SDK bietet Ihnen Folgendes:
    • ISdvGateway AIDL API für die Interprozesskommunikation.
    • Kommunikationsbibliotheken für die Netzwerkinteraktion.
    • Bequeme C-API für die native Dienstintegration.
  • Die ISdvGateway AIDL API wird vom SDV Gateway-Dienst und -Subsystem implementiert.
  • Der SDV Gateway-Dienst verwaltet:
    • Bidirektionale Diensterkennung.
    • Kommunikation mit Remote-SDV-Diensten.
    • Wichtige Geschäftslogik.
  • Das SDV Gateway-Subsystem stellt eine Verbindung zum Fahrzeugnetzwerk her.
  • Native Dienste, einschließlich der VHAL-Implementierung, können die ISdvGateway-AIDL-API direkt oder über die C-API des SDK verwenden.
  • Der VHAL-Proxy dient als Referenz-VHAL-Implementierung, die die VSIDL-Zuordnungsintegration umfasst.

Integrationsmodell für SDV Gateway in einem IVI-nativen Dienst

Das Integrationsmodell wird in Abbildung 2 dargestellt:

SDV Gateway-Integrationsmodell

Abbildung 2: SDV Gateway-Integrationsmodell.

SDV Gateway in einem IVI-nativen Dienst verwenden

Abbildung 3 zeigt die Verwendung des SDV-Gateways auf IVI:

SDV-Gateway auf IVI

Abbildung 3: SDV-Gateway auf IVI.

Preconditions

Starten Sie den Binder-Thread-Pool:

  • Die SDV Gateway-Clientbibliothek erfordert einen gestarteten Binder-Threadpool, um asynchrone Callbacks von Binder-Diensten zu empfangen.

  • Die API, die zum Erstellen eines SDV Gateway-Clients erforderlich ist, schlägt fehl, wenn kein Binder-Threadpool gestartet wird.

Native Gateway-Clientbibliothek einbinden

Die Native Gateway Client-Bibliothek bietet eine C-API. Fügen Sie eine Instanz von libsdvgatewayclient als Abhängigkeit hinzu, um die C-API zu verwenden:

cc_binary {
    name: "your_binary_name",
    srcs: ["main.cpp"],
    shared_libs: [
        "libsdvgatewayclient",
    ],
}

Native Gateway-Client laden

#include "libsdvgatewayclient.h"

Native Client-Instanz erstellen

ASDVGateway_Client* client;
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_new(
     &client, /*outStatus*/nullptr);

Nachdem der Client erstellt wurde, gilt Folgendes:

  • Speichert den Status für alle nachfolgenden Interaktionen mit dem Gateway-Dienst.

  • Dient als Kontext für alle Interaktionen mit anderen SDV-fähigen Diensten und wird als erster Parameter an die C-API-Funktionen übergeben.

Statuscodes und Fehlermeldungen

Die meisten C-API-Funktionen haben diese Definition:

ASDVGateway_StatusCode_t ApiFunctionName(..., ASDVGateway_Status_t* outStatus);

Um den Erfolg zu bewerten, können Sie den zurückgegebenen Statuscode prüfen, der vom Typ ASDVGateway_StatusCode_t ist. Sie können auch einen Zeiger auf eine Struktur übergeben, in der die Funktion den Statuscode und eine Fehlermeldung einfügen kann. Der Zeiger wird als letzter Parameter mit dem Namen outStatus übergeben. Ein Nullwert bedeutet, dass die Ausgabestruktur nicht verwendet wird.

Der Aufrufer muss die Speicherstatusstruktur für die Fehlermeldung zuweisen. Die Statusstruktur kann sowohl den Statuscode als auch eine Fehlermeldung enthalten. Ein Beispiel für das Abrufen der Fehlermeldung beim Erstellen eines neuen Clients ist verfügbar.

So messen Sie Erfolge:

  1. Prüfen Sie den zurückgegebenen Statuscode, der vom Typ ASDVGateway_StatusCode_t ist.

  2. Übergeben Sie einen Zeiger auf eine Struktur, in die die Funktion den Statuscode und eine Fehlermeldung einfügen kann.

    • Der Zeiger wird als letzter Parameter mit dem Namen outStatus übergeben.
    • Ein Nullwert bedeutet, dass die Ausgabestruktur nicht verwendet wird.
    • Der Aufrufer muss die Speicherstatusstruktur für die Fehlermeldung zuweisen.
    • Die Statusstruktur kann den Statuscode und eine Fehlermeldung enthalten.

Hier ist ein Beispiel dafür, wie Sie die Fehlermeldung für einen neuen Client abrufen:

#include <iostream>
#include <array>

struct StatusWithErrorMsg : ASDVGateway_Status_t {
    StatusWithErrorMsg() {
        // Ensure the base struct pointers point to our internal buffer
        errorMessage = errorMessageBuffer.data();
        maxErrorMessageSize = errorMessageBuffer.size();

        // Good practice: Zero-initialize the buffer
        errorMessageBuffer.fill(0);
    }

    std::array<char, 256> errorMessageBuffer;
};

// --- Execution ---

ASDVGateway_Client* client = nullptr;
StatusWithErrorMsg status;

// Initialize the client
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_new(&client, &status);

// Log Results
std::cout << "Returned statusCode:    " << statusCode << std::endl;
std::cout << "Status Struct Code:     " << status.statusCode << std::endl;
std::cout << "Status Error Message:   " << status.errorMessage << std::endl;

In diesem Fall gilt Folgendes:

  • Der Statuscode in der Statusstruktur hat denselben Wert wie der zurückgegebene Statuscode.

  • Die Fehlermeldung wird nur bis zum Zeichenlimit von maxErrorMessageSize (einschließlich des Null-Terminierungszeichens \0) ausgefüllt. Wenn kein Fehler auftritt (Statuscode ist OK), ist die Fehlermeldung ein leerer String.

Init-Kommunikation

Mit „Init comms“ wird die Kommunikation zwischen der Anrufer-App und anderen Apps über den SDV Comm Stack und das SDV Gateway initialisiert. Init-Kommunikation kann in beiden Kontexten aufgerufen werden::

  • Nachdem der Client erstellt wurde.

  • Vor allen Data Tunnel-, RPC- oder Service Discovery-Interaktionen.

Beispiel:

ASDVGateway_InitCommsParams_t params{
    .packageName         = "android.sdv.samples.gateway.client",
    .serviceBundleName   = "NativeTestApp",
    .serviceInstanceName = "default",
};

// Initialize communications and capture the status code
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_initComms(client, &params, &status);

// Recommended check
if (statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Failed to init comms: " << status.errorMessage << std::endl;
}

Diensterkennung

Sie können Benachrichtigungen erhalten, wenn Serviceeinheiten eines bestimmten Typs oder Namens registriert oder abgemeldet werden. Die Callback-Funktion wird in einem Thread benachrichtigt, der dem Gateway-Client gehört. Dieser Callback wird zunächst mit allen Dienst-Units ausgelöst, die direkt nach dem C-API-Aufruf registriert wurden. Nach dem ersten Trigger werden weiterhin Benachrichtigungen mit Updates gesendet, bis der Listener explizit abgemeldet wird.

class NativeSdvGatewayTestApp {
public:
    static void ServiceUnitChangeListenerCallback(
        const ASDVGateway_ServiceUnitChangeEventType eventType,
        const ASDVGateway_ServiceUnitDefinition* serviceUnitDefinition,
        void* userData
    ) {
        auto* testApp = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);

        if (testApp) {
            // Logic to react when services are registered or unregistered
            // e.g., testApp->handleServiceChange(eventType, serviceUnitDefinition);
        }
    }
};

// --- Listener Registration ---

// Ensure thisApp remains in scope as long as the listener is active
NativeSdvGatewayTestApp* thisApp = get_current_app_context();

ASDVGateway_UnitType_t unitType{
    .sdvPackageName     = "android.sdv.samples.sdv_gateway",
    .serviceBundleName  = "DtPublisher",
    .unitTypeName       = "TirePressure",
};

// Register the listener
ASDVGateway_Client_registerListenerForServiceUnitChangeByType(
    client,
    &unitType,
    NativeSdvGatewayTestApp::ServiceUnitChangeListenerCallback,
    static_cast<void*>(thisApp), // userData
    &listenerHandle,
    &status
);

// --- Cleanup ---

// Unregistering stops all notification to the callback function
ASDVGateway_Client_unregisterListenerForServiceUnitChangeByType(
    client,
    listenerHandle,
    &status
);

Wenn Sie die registrierten Serviceeinheiten ohne weitere Benachrichtigungen abrufen möchten, verwenden Sie die APIs ASDVGateway_Client_fetchServiceUnitsByType und ASDVGateway_Client_fetchServiceUnitsByName.

class NativeSdvGatewayTestApp {
public:
    /**
     * Callback triggered for each service unit found that matches the requested type.
     */
    static void FetchServiceUnitsCallback(
        const ASDVGateway_ServiceUnitDefinition* serviceUnitDefinition,
        void* userData
    ) {
        auto* app = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);

        if (serviceUnitDefinition) {
            // The service unit is registered with service discovery.
            // Example: processServiceUnit(serviceUnitDefinition);
        }
    }
};

// --- Execution ---

ASDVGateway_UnitType_t unitType{
    .sdvPackageName     = "android.sdv.samples.sdv_gateway",
    .serviceBundleName  = "DtPublisher",
    .unitTypeName       = "TirePressure",
};

// Context used to differentiate between various fetchServiceUnits calls
void* userData = static_cast<void*>(thisApp);

ASDVGateway_Client_fetchServiceUnitsByType(
    client,
    &unitType,
    NativeSdvGatewayTestApp::FetchServiceUnitsCallback,
    userData,
    &status
);

Der Callback wird synchron im Thread des Aufrufers während des ASDVGateway_Client_fetchServiceUnitsByType-API-Aufrufs ausgelöst. Verwenden Sie ASDVGateway_Client_fetchServiceUnitsByName, um die registrierten Serviceeinheiten nach Namen anstelle des Einheitentyps abzurufen:

ASDVGateway_StatusCode_t ASDVGateway_Client_fetchServiceUnitsByName(
    // [in]  Opaque pointer to a client object.
    const ASDVGateway_Client* client,

    // [in]  Pointer to a structure containing the package name,
    //       service bundle name, and service unit name.
    const ASDVGateway_UnitNameDiscoveryArgs_t* unitName,

    // [in]  Callback function to be called for each service unit
    //       definition registered. Called synchronously in the
    //       caller's thread before the fetch completes.
    ASDVGateway_FetchServiceUnitsCallback callback,

    // [in]  Optional value passed back as a parameter of the callback.
    void* userData,

    // [out] Optional pointer to status structure for result
    //       codes and error messages.
    ASDVGateway_Status_t* outStatus
);

RPC-Ablauf

Die Gateway-Clientbibliothek verarbeitet die TLS-Einstellungen (Transport Layer Security) für die Kommunikation mit anderen SDV-fähigen Apps. Insbesondere werden die für die Kommunikation erforderlichen TLS-Einstellungen abgerufen. So wird die TLS-Nutzung ermittelt:

  • TLS wird verwendet, wenn der SDV-Bootmodus LOCKED ist.

  • Nicht sichere Kommunikation wird verwendet, wenn der SDV-Boot-Modus UNLOCKED ist. Wenn TLS verwendet wird, generiert die Gateway-Clientbibliothek ein Schlüsselpaar, das nur dem App-Prozess bekannt ist. Der Client ruft die RPC-Anmeldedaten ab, um einen RPC-Server oder einen RPC-Client-Channel zu erstellen. Für RPC wird ein dediziertes SDV-RPC-VLAN verwendet. Die Gateway-Clientbibliothek ruft android_setprocnetwork auf, um das Standardnetzwerk des Prozesses während oder nach dem ASDVGateway_Client_initComms-Aufruf auf das SDV-RPC-VLAN umzustellen.

RPC-Verfügbarkeit

Ein Client, der für den Start beim frühen Booten konfiguriert ist, wird möglicherweise gestartet, bevor das SDV-RPC-VLAN verfügbar ist. Prüfen Sie, ob RPC verfügbar ist, bevor Sie versuchen, RPC-Server- oder RPC-Client-Sockets zu erstellen:

// Check if the RPC (Remote Procedure Call) service is available
bool isRpcAvailable = ASDVGateway_Client_isRpcAvailable(client);

if (isRpcAvailable) {
    // Proceed with RPC calls
} else {
    // Handle the case where RPC is not yet ready or available
}

oder

// Check if the process is correctly bound to the SDV RPC Network Interface/VLAN
bool isRpcNetworkBound = ASDVGateway_Client_isProcessBoundToSdvRpcNetworkInterface(client);

if (!isRpcNetworkBound) {
    // Usually implies the process isn't running on the correct network interface
    // or the VLAN configuration is missing.
    std::cerr << "Warning: Process is not bound to the SDV RPC VLAN." << std::endl;
}

Legen Sie mit ASDVGateway_Client_setClientNotificationCallback einen Listener für Client-Ereignisse fest, um benachrichtigt zu werden, wenn sich der Verfügbarkeitsstatus des RPC ändert. ASDVGateway_Client_isProcessBoundToSdvRpcNetworkInterface wird bevorzugt, wenn die App das Netzwerk des Prozesses wechselt, da sowohl geprüft wird, ob RPC verfügbar ist, als auch, ob der Prozess an das SDV-RPC-VLAN gebunden ist.

Standardnetzwerk des Prozesses ändern

Möglicherweise müssen Sie das SDV-RPC-VLAN als Standardnetzwerk des Prozesses ändern, um Sockets für internetgebundene Verbindungen zu öffnen. Rufen Sie ASDVGateway_Client_unbindProcessFromSdvRpcNetworkInterface und ASDVGateway_Client_bindProcessToSdvRpcNetworkInterface auf, um den Prozess vom SDV-RPC-VLAN zu trennen und wieder zu verbinden. Die beiden Aufrufe fungieren wie globale Schalter und schalten die Netzwerkschnittstelle um, an die Sockets für alle Threads des Prozesses gebunden sind.

// 1. Unbind: Sockets return to the "default" network interface (e.g., wlan0, eth0)
ASDVGateway_StatusCode_t unbindStatus =
    ASDVGateway_Client_unbindProcessFromSdvRpcNetworkInterface(client, &status);

// 2. Bind: Sockets are now bound to the dedicated SDV-RPC VLAN
ASDVGateway_StatusCode_t bindStatus =
    ASDVGateway_Client_bindProcessToSdvRpcNetworkInterface(client, &status);

// Validation
if (bindStatus == ASDVGateway_StatusCode_OK) {
    // Sockets are successfully bound to the SDV-RPC VLAN
}

RPC-Benachrichtigungen

Legen Sie den Client-Listener fest, um Benachrichtigungen zu Änderungen der RPC-Verfügbarkeit und zu Updates der Stammzertifikate zu erhalten, die von einem RPC-Server akzeptiert werden müssen:

class NativeSdvGatewayTestApp {
public:
    static void ClientNotificationCallback(
        ASDVGateway_ClientNotificationType_t notificationType,
        void* userData
    ) {
        auto* testApp = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);
        if (!testApp) return;

        switch (notificationType) {
            case ASDVGateway_ClientNotificationType_RootCertsChanged:
                std::cout << "onClientNotification: Root Certs Changed" << std::endl;
                // Handle certificate rotation logic here
                break;

            case ASDVGateway_ClientNotificationType_RpcAvailabilityChanged:
                std::cout << "onClientNotification: RPC Availability Changed" << std::endl;
                // Handle reconnection or UI updates here
                break;

            default:
                std::cout << "onClientNotification: Received Unknown Notification ("
                          << notificationType << ")" << std::endl;
                break;
        }
    }
};

// --- Registration ---

NativeSdvGatewayTestApp* thisApp = get_current_app_context();

// Register the notification callback to monitor system-level changes
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_setClientNotificationCallback(
    client,
    NativeSdvGatewayTestApp::ClientNotificationCallback,
    static_cast<void*>(thisApp), // userData
    &status
);

RPC-Serverablauf

Sie müssen Anmeldedaten verwenden, um den RPC-Server zu erstellen:

ASDVGateway_RpcCredentials_t* rpcCredentials = nullptr;

// Retrieve the credentials from the client
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_rpcCredentials(client, &rpcCredentials, &status);

// Validation: Differentiating between an error and a deliberate "insecure mode"
if (status.statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Error retrieving credentials: " << status.errorMessage << std::endl;
    return;
}

// Logic for choosing Security Credentials
if (rpcCredentials == nullptr) {
    // If the call succeeded but credentials are null, the system expects insecure communication
    std::cout << "Configuring for Insecure Mode" << std::endl;
} else {
    std::cout << "Configuring for Secure Mode:" << std::endl;
    std::cout << "  Private Key: " << (rpcCredentials->privateKeyPem ? "Present" : "Missing") << std::endl;
    std::cout << "  RootCerts:   " << rpcCredentials->rootCertsPem << std::endl;
    std::cout << "  CertChain:   " << rpcCredentials->certChainPem << std::endl;
    std::cout << "  SAN:         " << rpcCredentials->subjectAlternativeName << std::endl;
}

// --- Cleanup ---

// Release the memory once the RPC server/client is initialized
if (rpcCredentials != nullptr) {
    ASDVGateway_Client_deleteRpcCredentials(client, rpcCredentials);
}

Nachdem Sie den RPC-Server erstellt haben und der zugehörige Listening-Port bekannt ist, registrieren Sie den RPC-Server, damit er andere Apps erkennen kann:

ASDVGateway_RegisterRpcServerParams_t params{
    .serviceUnitName = "android-sdv-samples-sunroof-sunroof",
    .unitType = ASDVGateway_UnitType_t{
        .sdvPackageName     = "android.sdv.samples.sunroof",
        .serviceBundleName  = "SunroofServer",
        .unitTypeName       = "Sunroof",
    },
    .listeningPort = listeningPort,
    .serverUnitMetadata = ASDVGateway_ServerUnitMetadata_t{
        .version = 1,
    },
};

// Register the RPC server with the SDV Gateway
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_registerRpcServer(
    client,
    &params,
    &status
);

// Basic error handling
if (statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Failed to register RPC server: " << status.errorMessage << std::endl;
}

Wenn das System Root-Zertifikate zur Laufzeit aktualisiert (z. B. bei Änderungen des VM-Status), müssen RPC-Server ihre Liste der akzeptierten Zertifikate aktualisieren:

  • Legen Sie mit ASDVGateway_Client_setClientNotificationCallback einen Listener für Clientereignisse fest, um benachrichtigt zu werden, wenn die Stammzertifikate aktualisiert wurden.

  • Rufen Sie ASDVGateway_Client_rpcCredentials auf, um die aktualisierten Stammzertifikate abzurufen.

RPC-Clientablauf

So sieht der Ablauf für den RPC-Client aus:

ASDVGateway_FindRpcServerByNameParams_t params{
    .packageName        = "android.sdv.samples.cluster",
    .serviceBundleName  = "ClusterServer",
    .serviceUnitName    = "android-sdv-samples-cluster-cluster",
};

ASDVGateway_SocketAddress_t socketAddress;
ASDVGateway_RpcCredentials_t* rpcCredentials = nullptr;

// Perform the lookup to find the server's location and security requirements
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_findRpcServerByName(
    mClient,
    &params,
    &socketAddress,
    &rpcCredentials,
    &status
);

// Mandatory check: Distinguish between system errors and intentional "Insecure Mode"
if (status.statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Failed to find RPC server: " << status.errorMessage << std::endl;
    return;
}

// Logic for establishing the RPC channel
if (rpcCredentials == nullptr) {
    std::cout << "Connecting via Insecure Mode to "
              << socketAddress.address << ":" << socketAddress.port << std::endl;
} else {
    std::cout << "Connecting via Secure Mode to "
              << socketAddress.address << ":" << socketAddress.port << std::endl;

    std::cout << "  RootCerts: " << rpcCredentials->rootCertsPem << std::endl;
    std::cout << "  SAN:       " << rpcCredentials->subjectAlternativeName << std::endl;
    // Note: privateKeyPem and certChainPem are typically used if the client
    // needs to perform mutual TLS (mTLS).
}

// Cleanup: Release the credential memory once the RPC channel is established
if (rpcCredentials != nullptr) {
    ASDVGateway_Client_deleteRpcCredentials(client, rpcCredentials);
}

Publisher erstellen und Nachrichten veröffentlichen

Mit der ASDVGateway_Client_createPublication API wird eine Publisher-Serviceeinheit über die AIDL-Schnittstelle beim SDV-Gateway registriert und eine Fast Message Queue (FMQ) im App-Prozess erstellt. Der Binder wird nur zum Einrichten des FMQ verwendet, nicht zum Schreiben von Nachrichten. Die ASDVGateway_Client_publishMessages API wird dann verwendet, um Nachrichten in der erstellten Veröffentlichung zu veröffentlichen. Dazu gehört, dass Sie in das FMQ der Publikation schreiben und benachrichtigen, dass Nachrichten verfasst wurden.

ASDVGateway_CreatePublicationParams_t params{
    .serviceUnitName = "mirror-position-adjust-impl-1",
    .unitType = ASDVGateway_UnitType_t{
        .sdvPackageName     = "android.sdv.samples.sdv_gateway",
        .serviceBundleName  = "DtPublisher",
        .unitTypeName       = "MirrorPositionAdjust",
    },
    .publisherUnitMetadata = ASDVGateway_PublisherUnitMetadata_t{
        .version          = 1,
        .messageSizeBytes = 64,
        .messageCount     = 16,
    },
};

ASDVGateway_PublicationMetadata_t metadata;

// 1. Create the Publication (allocates resources on the gateway)
ASDVGateway_StatusCode_t createStatus = ASDVGateway_Client_createPublication(
    client,
    &params,
    &metadata,
    &status
);

if (status.statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Failed to create publication: " << status.errorMessage << std::endl;
    return;
}

// 2. Publish Messages
// Note: serializedMessage should contain your encoded protobuf data
std::vector<uint8_t> serializedMessage;

ASDVGateway_Client_publishMessages(
    client,
    serializedMessage.data(),
    serializedMessage.size(),
    metadata.publicationId,
    &status
);

Abonnenten mit Benachrichtigungs-Listener erstellen

Wenn Sie eine Publikation abonnieren und Data Tunnel-Benachrichtigungen (z. B. zur Nachrichtenverfügbarkeit) erhalten möchten, verwenden Sie die ASDVGateway_Client_subscribeToPublicationByName API. Mit dieser API wird auch die FMQ zum Lesen veröffentlichter Nachrichten eingerichtet. Sie können einen Benachrichtigungs-Callback entweder während des Abovorgangs oder danach konfigurieren.

#include <map>
#include <memory>
#include <iostream>

class NativeSdvGatewayTestApp {
public:
    struct SubscriptionContext {
        int32_t subscriptionId;
        std::string topicName;
        // Add other context-specific data here (e.g., counters, buffers)
    };

    /**
     * Callback triggered when new data is published to a subscribed topic.
     */
    static void SubscriptionNotificationCallback(
        const ASDVGateway_SubscriptionNotificationData_t* notification,
        void* userData
    ) {
        // The SDV Gateway client passes back the userData pointer.
        // We ensure validity by managing the lifecycle of SubscriptionContext
        // within the mSubscriptions map.
        auto* ctx = reinterpret_cast<SubscriptionContext*>(userData);

        if (ctx && notification) {
            // React to data being available for the subscription.
            // Example: handleIncomingData(notification->data, notification->size);
            std::cout << "Notification received for sub ID: " << ctx->subscriptionId << std::endl;
        }
    }

private:
    // Maps subscription handles/IDs to their respective contexts
    std::map<int32_t, std::unique_ptr<SubscriptionContext>> mSubscriptions;
};

// --- Usage ---
NativeSdvGatewayTestApp app;

Beim Abonnieren können Sie neben dem Namen der Serviceeinheit des Publishers zusätzliche Optionen angeben. Mit diesen Optionen können Sie die zuletzt veröffentlichte Nachricht vor dem Abonnieren abrufen und das Mindestzeitintervall zwischen Benachrichtigungen festlegen.

ASDVGateway_SubscribeToPublicationByNameParams_t params{
    .sdvVmName           = "vm1",
    .packageName         = "test.package.impl.name",
    .serviceBundleName   = "TestBundleImpl",
    .serviceUnitName     = "GrpcServerImpl",
};

ASDVGateway_Subscriber_Options_t options{
    // Ensure we receive the most recent message immediately upon subscribing
    .flags         = ASDVGATEWAY_SUBSCRIBER_OPTIONS_FLAG_FETCHLASTMESSAGE,
    .minIntervalMs = 0, // No rate limiting; receive updates as they happen
};

// 1. Prepare the context for the callback
auto subCtx = std::make_unique<SubscriptionContext>();
ASDVGateway_PublicationMetadata_t metadata{};

// 2. Perform the subscription
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_subscribeToPublicationByName(
    client,
    &params,
    &options,
    NativeSdvGatewayTestApp::SubscriptionNotificationCallback,
    static_cast<void*>(subCtx.get()), // Pass the raw pointer as userData
    &metadata,
    &status
);

// 3. Validation and Lifecycle Management
if (status.statusCode != ASDVGateway_StatusCode_OK) {
    // Error handling: subCtx will be automatically deleted here
    std::cerr << "Subscription failed: " << status.errorMessage << std::endl;
    return;
}

// Store the context using the publicationId as the key.
// Once moved, the map owns the lifetime of subCtx.
app.mSubscriptions.emplace(metadata.publicationId, std::move(subCtx));

Verwenden Sie die ASDVGateway_Client_readAvailableMessages API, um Nachrichten aus der Publikation zu lesen:

uint32_t messagesAvailable = 0;

// 1. Check how many messages are waiting in the queue
ASDVGateway_StatusCode_t availStatus = ASDVGateway_Client_availableToRead(
    client,
    metadata.publicationId,
    &messagesAvailable,
    &status
);

if (status.statusCode != ASDVGateway_StatusCode_OK) {
    // Error handling for availability check
    return;
}

if (messagesAvailable == 0) {
    // No messages available for this publication at this time
    return;
}

// 2. Prepare the buffer
// metadata.messageSizeBytes was provided during the publication/subscription setup
std::vector<uint8_t> bytesForMessages;
bytesForMessages.resize(metadata.messageSizeBytes * messagesAvailable);

// 3. Read the messages from the Gateway into your local buffer
uint32_t actualMessageCount = 0; // Filled by the SDK with the number of messages read
ASDVGateway_StatusCode_t readStatus = ASDVGateway_Client_readAvailableMessages(
    client,
    metadata.publicationId,
    bytesForMessages.data(),
    bytesForMessages.size(),
    &actualMessageCount,
    &status
);

if (status.statusCode == ASDVGateway_StatusCode_OK) {
    // Successfully read 'actualMessageCount' messages
    // Process bytesForMessages...
}

Verwenden Sie die ASDVGateway_Client_setNotificationCallbackForPublicationId API, um den Callback nach dem Abonnieren festzulegen:

ASDVGateway_StatusCode_t ASDVGateway_Client_setNotificationCallbackForPublicationId(
    // [in]  Opaque pointer to the client object.
    const ASDVGateway_Client* client,

    // [in]  Identifies the specific publication to monitor.
    const int32_t publicationId,

    // [in]  Function pointer triggered when new data is available
    //       for the specified publication.
    ASDVGateway_SubscriptionNotificationCallback notificationCallback,

    // [in]  Optional user-defined context passed back to the callback.
    void* notificationCallbackUserData,

    // [out] Optional pointer to a status structure for result codes
    //       and error messages.
    ASDVGateway_Status_t* outStatus
);

Init-Dienst einrichten

Angenommen, Ihr Dienst heißt native_sdv_gateway_client_service, die ausführbare Datei befindet sich unter /vendor/bin/native_sdv_gateway_client_service und Sie verwenden vendor_sdv_services als Android-UID (AID) zum Ausführen des Dienstes.

Kein nativer Dienst sollte die AID für Android-Apps verwenden. Hier muss eine AID im reservierten Bereich für Anbieterservices verwendet werden. Mit dieser Einrichtung können Sie den folgenden Init-Dienst definieren:

service native_sdv_gateway_client_service /vendor/bin/native_sdv_gateway_client_service
    class core
    user vendor_sdv_services
    group inet
    disabled
    oneshot

Wo

  • vendor_sdv_services ist die AID, die für den Dienst erstellt oder ausgewählt wurde.
  • group inet ist für SDV-Gateway-Clients erforderlich, um die Netzwerkschnittstelle für die Kommunikation zwischen SDV-VMs zu verwenden. Bei Bedarf können weitere Gruppen hinzugefügt werden.
  • In diesem Beispiel werden disabled und oneshot verwendet. Möglicherweise müssen Sie die Dienstoptionen für Ihren Dienst anpassen. Starten Sie den Dienst nach sdv_gateway.

SELinux-Regeln für den Dienst erstellen

Um die SDV Gateway APIs zu verwenden, benötigen Sie die folgenden SELinux-Regeln für den Dienst:

# Define the domain for the service
type native_sdv_gateway_client_service, domain;

# Define the executable file type on the vendor partition
type native_sdv_gateway_client_service_exec, exec_type, file_type, vendor_file_type;

# Macro to transition from 'init' to this service's domain upon execution
init_daemon_domain(native_sdv_gateway_client_service)

# Macro to grant the necessary permissions to communicate with the SDV Gateway
sdv_gateway_client_domain(native_sdv_gateway_client_service)

In den SELinux-Regeln:

  • Mit init_daemon_domain kann der Dienst über init gestartet werden.

  • sdv_gateway_client_domain bietet alle erforderlichen SELinux-Berechtigungen für die Interaktion mit dem SDV-Gateway. Die folgende Zeile gewährt der ausführbaren Datei diese Regeln:

    /vendor/bin/native_sdv_gateway_client_service    u:object_r:native_sdv_gateway_client_service_exec:s0
    

Codebeispiel

Weitere Informationen zum Ausführen des nativen Codebeispiels, das zeigt, wie die C-APIs dokumentiert werden, finden Sie unter system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/README.md.

SDV-Gateway in IVI-Java-App

Das Interaktionsmodell für ein SDV-Gateway auf IVI wird in diesem Diagramm veranschaulicht:

SDV-Gateway in IVI-Java-App

Abbildung 4: SDV Gateway in der IVI-Java-App.

Das Modell:

  • Leitet alle Aufrufe an die JNI-Ebene weiter.
  • Verbindet die Java- und C-API.
  • Definiert AIDL-Interaktionen mit dem ISdvGateway:
    • Init-Kommunikation
    • RPC-Server suchen oder erstellen
    • Pub/Sub erstellen
    • Data Tunnel-Interaktionen für die Diensterkennung ausführen
    • Benachrichtigungen erhalten (z. B. wenn Daten verfügbar sind)
    • Nachrichten lesen und schreiben (FMQ für Pub/Sub) für die Erstellung von Schlüsselpaaren und Zertifikaten für TLS

Gateway-Clientbibliotheken einbinden

Die Java-Bibliothek und der JNI-Wrapper für die libsdvgatewayclient C-API werden in einem APEX auf dem IVI-Ziel installiert. Fügen Sie eine Kompilierdauer-Abhängigkeit für den Java-Bibliotheks-Stub, die Java-Bibliothek, die zur Laufzeit verwendet werden muss, und das erforderliche APEX mit der Java-Bibliothek hinzu.

android_app {
    name: "YourAppName",
    // ...
    static_libs: [
        "libsdvgatewayclient-java",
    ],
    libs: [
        "libsdvgatewayclient-java-sdk.stubs",
    ],
    uses_libs: [
        "libsdvgatewayclient-java-sdk",
    ],
    required: [
        "com.sdv.google.gateway.client",
    ],
    // ...
}

Bei einer nicht gebündelten App, die außerhalb des SDV-Baums erstellt wurde, ist der Vorgang ähnlich:

  1. Kopieren Sie die generierte JAR-Datei für den Java-Bibliotheks-Stub und die unterstützende JAR-Datei für RPC in den Ordner „app/libs“. Weitere Informationen finden Sie unter system/software_defined_vehicle/sdv_gateway/libsdvgatewayclient_apex/README.md.

  2. Fügen Sie das JAR mit den Stubs als reine Kompilierungsabhängigkeit hinzu. Aktualisieren Sie beispielsweise die Gradle-Konfigurationen, sodass sie von den Stubs abhängen, indem Sie dem Abschnitt dependencies den Eintrag compileOnly hinzufügen:

    dependencies {
        // The library supporting functions for SDK RPC.
        // Statically linked into the app APK.
        implementation(files("libs/libsdvgatewayclient-java.jar"))
    
        // Stub of the SDV-Gateway client library.
        // Used only for compilation; the real implementation is provided 
        // by the com.sdv.google.gateway.client APEX at runtime.
        compileOnly(files("libs/libsdvgatewayclient-java-sdk.jar"))
    }
    
  3. Fügen Sie die Java-Bibliothek dem App-Abschnitt der Datei AndroidManifest.xml hinzu.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
        <application>
    
            <!--
              Declares that this app requires the SDV Gateway client library.
              The 'android:required="true"' attribute ensures the app won't
              install/run if the library is missing from the system.
            -->
            <uses-library
                android:name="libsdvgatewayclient-java-sdk"
                android:required="true" />
    
        </application>
    
    </manifest>
    

Bibliotheken laden

Erstellen Sie ein SdvGatewayClient-Objekt (von der Clientbibliothek bereitgestellt):

import google.sdv.gateway.client.SdvGatewayClient;

// --- Inside your Activity, Service, or ViewModel ---

// Initialize the SDV Gateway Client
SdvGatewayClient gatewayClient = new SdvGatewayClient();

Init-Kommunikation

Rufen Sie initComms() mit einem App-Namen als Dienstbündelnamen auf:

// Define a unique name for your service bundle (usually constant)
private static final String SERVICE_BUNDLE_NAME = "MySdvServiceBundle";

// ... inside an Activity or Service context ...

try {
    // Initialize communications with the SDV Gateway
    // context.getPackageName() provides "android.sdv.samples.gateway.client" or similar
    gatewayClient.initComms(context.getPackageName(), SERVICE_BUNDLE_NAME);

    Log.i("SDV_GATEWAY", "Communications initialized successfully");
} catch (Exception e) {
    // Unlike the C API which uses status codes,
    // the Java SDK often throws exceptions for initialization failures.
    Log.e("SDV_GATEWAY", "Failed to initialize communications", e);
}

Diensterkennung

Die Java API enthält Methoden für folgende Aufgaben:

  • Sie werden benachrichtigt, wenn Dienstgeräte mit einem bestimmten Gerätetyp (oder alternativ mit einem bestimmten Namen) registriert oder abgemeldet werden.

  • Aktuelle Serviceeinheiten mit einem bestimmten Einheitstyp auflisten (oder alternativ einen bestimmten Namen für den Einheitstyp abgleichen). Wenn Sie über Änderungen an Serviceeinheiten benachrichtigt werden möchten, erstellen Sie einen Listener:

import google.sdv.gateway.client.ServiceUnitChangeListener;
import google.sdv.gateway.client.ServiceUnitChangeEventType;
import google.sdv.gateway.client.ServiceUnitDefinition;

// --- Implementation ---

ServiceUnitChangeListener listener = new ServiceUnitChangeListener() {
    @Override
    public void onServiceUnitChanged(
        ServiceUnitChangeEventType eventType,
        ServiceUnitDefinition serviceUnitDefinition
    ) {
        // This is triggered when services matching your criteria
        // are registered or unregistered on the vehicle network.
        if (eventType == ServiceUnitChangeEventType.REGISTERED) {
            // Handle new service discovery
        } else if (eventType == ServiceUnitChangeEventType.UNREGISTERED) {
            // Handle service removal
        }
    }
};

Die Java-Methode addListenerForServiceUnitChangeByName benachrichtigt den Listener:

// 1. Register the listener for a specific service unit by name
AutoCloseable handle = gatewayClient.addListenerForServiceUnitChangeByName(
    new UnitNameDiscoveryArgs(
        "",                                   // sdvVmName (empty for local/auto-discovery)
        "android.sdv.samples.cluster",        // sdvPackageName
        "ClusterServer",                      // serviceBundleName
        "android-sdv-samples-cluster-cluster" // serviceUnitName
    ),
    listener
);

// --- Later in the application lifecycle ---

// 2. To stop receiving notifications and clean up resources, close the handle.
try {
    if (handle != null) {
        handle.close();
    }
} catch (Exception e) {
    Log.e("SDV_GATEWAY", "Error while closing the listener handle", e);
}

Alternativ können Sie die Java-Methode addListenerForServiceUnitChangeByType verwenden, um den Listener zu benachrichtigen, wenn Dienste mit dem angegebenen Einheitentyp registriert oder abgemeldet werden:

// 1. Register a listener based on the Service Unit Type
AutoCloseable handle = gatewayClient.addListenerForServiceUnitChangeByType(
    new UnitType(
        "com.android.testapp.sdvcarmonitor", // sdvPackageName
        "SunroofRpcServer",                  // serviceBundleName
        "Sunroof"                            // unitTypeName
    ),
    listener
);

// --- Execution Loop / Lifecycle ---

// 2. To stop receiving notifications and clean up memory, close the handle.
// This effectively unregisters the listener from the SDV Gateway.
try {
    if (handle != null) {
        handle.close();
    }
} catch (Exception e) {
    Log.e("SDV_GATEWAY", "Failed to close the service unit listener handle", e);
}

Bei addListenerForServiceUnitChangeByName und addListenerForServiceUnitChangeByType wird der Listener nach dem Hinzufügen über alle registrierten Serviceeinheiten benachrichtigt. Wenn Sie nur registrierte Serviceeinheiten nach Name oder Typ abrufen möchten, verwenden Sie die Java-APIs listServiceUnitsByName und listServiceUnitsByType:

import google.sdv.gateway.client.ServiceUnitDefinition;
import google.sdv.gateway.client.UnitNameDiscoveryArgs;
import google.sdv.gateway.client.UnitType;

// --- 1. Synchronous Lookup by Specific Name ---
ServiceUnitDefinition[] definitionsByName = gatewayClient.listServiceUnitsByName(
    new UnitNameDiscoveryArgs(
        "",                                   // sdvVmName
        "android.sdv.samples.cluster",        // sdvPackageName
        "ClusterServer",                      // serviceBundleName
        "android-sdv-samples-cluster-cluster" // serviceUnitName
    )
);

// --- 2. Synchronous Lookup by Service Type ---
ServiceUnitDefinition[] definitionsByType = gatewayClient.listServiceUnitsByType(
    new UnitType(
        "com.android.testapp.sdvcarmonitor", // sdvPackageName
        "SunroofRpcServer",                  // serviceBundleName
        "Sunroof"                            // unitTypeName
    )
);

// Example processing
if (definitionsByType.length > 0) {
    ServiceUnitDefinition firstSunroof = definitionsByType[0];
    // Proceed to connect...
}

RPC-Serverablauf

SDV Gateway-Clients in IVI-Systemen verwenden Google Remote Procedure Call (gRPC) für die Kommunikation mit SDV-Diensten. Diese Interaktionen basieren auf Protodefitionen aus dem VSIDL-Katalog, die mit denen in SDV Core übereinstimmen oder ihnen ähneln. Für Java-Apps wird gRPC-Java als Implementierung verwendet. Für einen App-Server wird eine Beispiel-Proto-Definition für den Server (sunroof.proto) bereitgestellt.

service Sunroof {
    /**
     * Retrieves the current state of the sunroof (e.g., position, tilt, status).
     *
     * @param .google.protobuf.Empty - No input parameters required.
     * @return SunroofStateResponse - The current telemetry data for the sunroof.
     */
    rpc GetSunroofState(.google.protobuf.Empty) returns (SunroofStateResponse) {}
}

Verknüpfen Sie die entsprechende Proto-Bibliothek und definieren Sie den Dienst:

import com.android.sdv.sdvgrpclibrary.SunroofGrpc;
import com.android.sdv.sdvgrpclibrary.SunroofStateResponse; // Assuming this is the generated class
import com.google.protobuf.Empty;
import io.grpc.stub.StreamObserver;

/**
 * Implementation of the Sunroof gRPC service.
 * This class handles the logic for the RPCs defined in your .proto file.
 */
static class SunroofGrpcImpl extends SunroofGrpc.SunroofImplBase {

    @Override
    public void getSunroofState(Empty request, StreamObserver<SunroofStateResponse> responseObserver) {
        // 1. Fetch current sunroof data (e.g., from a Hardware Abstraction Layer)
        int currentPosition = 50; // Example value: 50% open

        // 2. Build the Protobuf response message
        SunroofStateResponse response = SunroofStateResponse.newBuilder()
                .setPercentageOpen(currentPosition)
                .build();

        // 3. Send the response to the client using the observer
        responseObserver.onNext(response);

        // 4. Close the stream to signal that the RPC is finished
        responseObserver.onCompleted();
    }
}

Registrieren Sie den gRPC-Server mit sicheren und unsicheren Channel-Anmeldedaten:

// 1. Define the type signature for the service
UnitType unitType = new UnitType(
    "com.android.testapp.sdvcarmonitor", // sdvPackageName
    "SunroofRpcServer",                  // serviceBundleName
    "Sunroof"                            // typeName (Unit Type)
);

// 2. Register the RPC server with the Gateway
// The gateway creates a mapping between the ServiceUnitName and your implementation.
server = gatewayClient.registerRpcServer(
    "SunroofRpcServerImpl-1",                       // serviceUnitName (Unique instance name)
    unitType,                                       // unitType defined earlier
    "SUNROOF_GRPC_SERVER_VALUE_HOLDER".getBytes(),  // appMetadataValueHolder (Static discovery data)
    1,                                              // appMetadataVersion
    new SunroofGrpcImpl()                           // The actual gRPC service implementation
);

Intern erstellt die Clientbibliothek das gRPC-Serverobjekt SdvGatewayClient.java und verarbeitet auch Aktualisierungen der Stammzertifikate:

// 1. Initialize credentials (Insecure for dev, TLS for production)
ServerCredentials serverCredentials = InsecureServerCredentials.create();

// 2. Build and start the OkHttp-based gRPC server
final int bindAnyPort = 0;
final Server server = OkHttpServerBuilder
    .forPort(bindAnyPort, serverCredentials)
    .addService(gRpcServerImplementation) // Your SunroofGrpcImpl
    .build()
    .start();

// The assigned port can now be retrieved using server.getPort()
int actualPort = server.getPort();

// 3. Prepare the JNI data structure
// This object mirrors the ASDVGateway_ServiceUnitDefinition_t C struct
JniServiceUnitDefinition definition = new JniServiceUnitDefinition();

// Fill the RPC service definition params (Port, Name, Type, etc.)
definition.setPort(actualPort);
definition.setServiceUnitName("SunroofRpcServerImpl-1");

// 4. Perform the cross-language call
// This jumps from Java -> JNI -> ASDVGateway_Client_registerRpcServer (C API)
mJniClient.nativeRegisterRpcServer(definition);

RPC-Clientablauf

Dieses Codebeispiel enthält die Server-Proto-Definition (tpms.proto) für den Server, mit dem die App als Client eine Verbindung herstellt:

/**
 * The TPMS service provides real-time pressure and temperature
 * data for all tires on the vehicle.
 */
service Tpms {
    /**
     * Returns the full state of all monitored tires.
     */
    rpc GetTpmsState(.google.protobuf.Empty) returns (TpmsStateResponse) {}

    /**
     * A filtered query that returns only the tires
     * below the recommended pressure threshold.
     */
    rpc GetLowTires(.google.protobuf.Empty) returns (LowTiresResponse) {}
}

Link zur entsprechenden Proto-Bibliothek:

import com.android.sdv.sdvgrpclibrary.TpmsGrpc;
import io.grpc.ManagedChannel;

// --- Inside your Client Application ---

// 1. Request a ManagedChannel from the Gateway for a specific service unit
ManagedChannel managedChannel = gatewayClient.connectToRpcServerByName(
    "",                                     // sdvVmName (empty for local/auto-lookup)
    "android.sdv.samples.cluster",          // packageName
    "ClusterServer",                        // serviceBundleName
    "android-sdv-samples-cluster-cluster"  // serviceUnitName
);

// 2. Use the channel to create a gRPC stub (e.g., for the TPMS service)
TpmsGrpc.TpmsBlockingStub tpmsStub = TpmsGrpc.newBlockingStub(managedChannel);

// 3. Now you can call RPC methods directly
// TpmsStateResponse response = tpmsStub.getTpmsState(Empty.getDefaultInstance());

Intern wird die ASDVGateway_Client_findRpcServerByName API aufgerufen, um den RPC-Server zu finden. Wenn der RPC-Server gefunden wird, wird der verwaltete Channel im unsicheren Modus erstellt oder für die Verwendung der TLS-Konfiguration vorgesehen, ähnlich wie beim RPC-Serverfluss, je nach Service Discovery-Konfiguration. Die App erstellt die Stubs mit dem ManagedChannel-Objekt und ruft Servermethoden auf:

import com.android.sdv.sdvgrpclibrary.TpmsGrpc;
import com.android.sdv.sdvgrpclibrary.TpmsStateResponse;
import com.google.protobuf.Empty;
import io.grpc.stub.MetadataUtils;

// 1. Create a "Blocking Stub" from the existing ManagedChannel.
// We apply an interceptor to attach mandatory metadata (headers)
// required by the SDV Gateway for authorization.
TpmsGrpc.TpmsBlockingStub tpmsStub = TpmsGrpc.newBlockingStub(managedChannel)
    .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(mMetadata));

// 2. Execute the RPC call.
// Because this is a "BlockingStub," the thread will wait here until
// the vehicle service responds or times out.
TpmsStateResponse tpmsStateResponse = tpmsStub.getTpmsState(Empty.getDefaultInstance());

// 3. Extract the domain-specific state object from the Protobuf response.
// 'newState' can now be used to update your UI or application logic.
newState = tpmsStateResponse.getTpmsState();

Standardnetzwerk des Prozesses ändern

Möglicherweise müssen Sie das SDV-RPC-VLAN als Standardnetzwerk des Prozesses ändern, um Sockets für internetgebundene Verbindungen zu öffnen. Rufen Sie unbindProcessFromSdvRpcNetworkInterface und bindProcessToSdvRpcNetworkInterface auf, um den Prozess vom SDV-RPC-VLAN zu entbinden und neu zu binden. Die beiden Aufrufe fungieren als globale Schalter, mit denen die Netzwerkschnittstelle, an die Sockets gebunden sind, für alle Threads des Prozesses umgeschaltet wird.

// 1. Redirect all socket traffic from this process to the SDV-RPC VLAN.
// This call is required for making RPC calls or hosting RPC services for SDV.
gatewayClient.bindProcessToSdvRpcNetworkInterface();

// --- Process is now communicating over the SDV-RPC interface ---

// 2. Revert the process network binding back to the "default" interface.
// This allows the app to access internet resources again.
gatewayClient.unbindProcessFromSdvRpcNetworkInterface();

Publisher erstellen und Nachrichten veröffentlichen

// 1. Define the interface and type for the publication
UnitType unitType = new UnitType(
    "android.sdv.samples.tires.interface", // sdvPackageName
    "TirePressurePublisherInterface",      // serviceBundleName
    "TirePressure"                         // typeName
);

// 2. Configure the Publisher's buffer and message constraints
PublisherUnitMetadata publisherUnitMetadata = new PublisherUnitMetadata(
    1,   // version
    64,  // message size in bytes (fixed size for performance)
    128  // max message count (buffer depth)
);

// 3. Create the Publication instance through the Gateway
Publisher tirePublisher = gatewayClient.createPublication(
    "tire-pressure-service-unit-name",
    unitType,
    publisherUnitMetadata
);

// 4. Prepare and publish a message
// In a real app, you would encode your sensor data into this byte array
byte[] msg = new byte[publisherUnitMetadata.messageSizeBytes];

// ... fill msg with data ...

tirePublisher.publish(msg);

Intern basieren die Java-APIs createPublication und „publish“ auf den nativen APIs ASDVGateway_Client_createPublication und ASDVGateway_Client_readAvailableMessages. Ausführliche Informationen zur C-API finden Sie unter SDV Gateway in einem IVI-nativen Dienst verwenden. Das Publisher-Objekt bietet einen Kontext zum Schreiben von Nachrichten und zum Verwalten des Lebenszyklus der Publikation.

Abonnent mit Benachrichtigungs-Listener erstellen

Mit der Java API kann ein Listener als Parameter an die Methode „subscribe to publication“ übergeben werden. Die Methode gibt ein Subscription-Objekt zurück.

  • Listener wird benachrichtigt, wenn Daten für die abonnierte Publikation verfügbar sind.

  • Subscription fungiert als Kontextobjekt und kann zum Lesen von Nachrichten und zum Schließen des Abos verwendet werden.

// 1. Define the Listener to handle incoming data notifications
SubscriptionNotificationListener listener = new SubscriptionNotificationListener() {
    @Override
    public void onSubscriptionNotification(
        Subscription subscription,
        SubscriptionNotificationType notificationType
    ) {
        // Only process if the notification indicates new data is ready
        if (notificationType != SubscriptionNotificationType.DataAvailable) {
            return;
        }

        // Read the message from the subscription buffer
        byte[] content = subscription.readMessage();

        // Note: This callback often runs on a background thread provided by the SDK.
        // If you need to update the UI, use a Handler or View.post().
        processTireData(content);
    }
};

// 2. Subscribe to the publication by its unique name
Subscription tireSubscription = gatewayClient.subscribeToPublicationByName(
    "",                                  // sdvVmName (empty for auto-lookup)
    "android.sdv.samples.dt_publisher",  // packageName
    "SdvGatewayDtPublisher",             // serviceBundleName
    "tire",                              // serviceUnitName
    listener
);

// 3. Read Messages
// You can poll or register callbacks for new messages.
// When polling the message, call this method in a loop.
// When registering callbacks, call this method to get the message payload.
byte[] manualContent = tireSubscription.readMessage();

Codebeispiel

Listener-Callbacks werden in einem einzelnen Thread aufgerufen, der vom Client verwaltet wird. So wird die Verarbeitung in Listenern minimiert, um Verzögerungen beim Empfang von Benachrichtigungen für andere Abos zu vermeiden. Die Java-Ebene nutzt eine C-API für die Aboverwaltung, die Verarbeitung von Benachrichtigungen und den Nachrichtenabruf. Ein Beispiel für die API-Nutzung finden Sie in der Java-Beispielanwendung in der Binärdatei unter system/software_defined_vehicle/samples/sdv_gateway/README.md.

Erforderliche Berechtigungen

Für den Aufruf der SDV Gateway Client API sind keine speziellen Berechtigungen erforderlich, aber Sie müssen die entsprechenden SELinux-Regeln für Ihre Apps anwenden.

  • Für Data Tunnel-Abos und ‑Veröffentlichungen sind keine Berechtigungen erforderlich.
  • Für SDV RPC benötigen Sie die folgenden Berechtigungen:
    • android.permission.INTERNET
    • android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS

Für android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS benötigen Sie außerdem eine Zulassungslistendatei unter etc/permissions in derselben Partition wie Ihre App. Für die SdvCarMonitorTestApp (Paketname com.android.testapp.sdvcarmonitor) sieht die Datei beispielsweise so aus:

<?xml version="1.0" encoding="utf-8"?>

<permissions>
    <privapp-permissions package="com.android.testapp.sdvcarmonitor">
        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
    </privapp-permissions>
</permissions>

SELinux-Regeln

Für die Verwendung von SDV Gateway-APIs benötigen Java-Apps dieselben Berechtigungen wie die nativen Dienste. Gewähren Sie diese Berechtigungen mit dem SELinux-Makro sdv_gateway_client_domain():

sdv_gateway_client_domain(my_oem_sdv_gateway_client_app)

Der OEM definiert die my_oem_sdv_gateway_client_app-Domain für die Java-Apps, die das SDV-Gateway verwenden dürfen. Verwenden Sie das SDV-Gateway nur über System- und privilegierte Apps.

Codespeicherorte

Den Quellcode für das SDV Gateway finden Sie unter system/software_defined_vehicle/sdv_gateway/. Sie können SDV Gateway-Beispiele für Folgendes abrufen:

  • Client-C-API: system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/
  • Client-Java-API: system/software_defined_vehicle/samples/sdv_gateway/SdvCarMonitorTestApp/