Expand Minimize Picture-in-picture Power Device Status Voice Recognition Skip Back Skip Forward Minus Plus Play Search
Internet Explorer alert
This browser is not recommended for use with smartdevicelink.com, and may not function properly. Upgrade to a different browser to guarantee support of all features.
close alert
To Top Created with Sketch. To Top
To Bottom Created with Sketch. To Bottom
iOS Guides
Using App Services

Using App Services (RPC v5.1+)

App services is a powerful feature enabling both a new kind of vehicle-to-app communication and app-to-app communication via SDL.

App services are used to publish navigation, weather and media data (such as temperature, navigation waypoints, or the current playlist name). This data can then be used by both the vehicle head unit and, if the publisher of the app service desires, other SDL apps. Creating an app service is covered in another guide.

Vehicle head units may use these services in various ways. One app service for each type will be the "active" service to the module. For media, for example, this will be the media app that the user is currently using or listening to. For navigation, it would be a navigation app that the user is using to navigate. For weather, it may be the last used weather app, or a user-selected default. The system may then use that service's data to perform various actions (such as navigating to an address with the active service or to display the temperature as provided from the active weather service).

An SDL app can also subscribe to a published app service. Once subscribed, the app will be sent the new data when the app service publisher updates its data. This guide will cover subscribing to a service. Subscribed apps can also send certain RPCs and generic URI-based actions (see the section Sending an Action to a Service Provider, below) to your service.

Currently, there is no high-level API support for using an app service, so you will have to use raw RPCs for all app service related APIs.

Getting and Subscribing to Services

Once your app has connected to the head unit, you will first want to be notified of all available services and updates to the metadata of all services on the head unit. Second, you will narrow down your app to subscribe to an individual app service and subscribe to its data. Third, you may want to interact with that service through RPCs, or fourth, through service actions.

Note

Please note that if you are integrating an sdl_ios version less than v6.3, the example code in this guide will not work. We recommend updating to the latest release version.

1. Getting and Subscribing to Available Services

To get information on all services published on the system, as well as on changes to published services, you will use the SystemCapabilityManager.

id subscribedObserver = [self.sdlManager.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeAppServices withUpdateHandler:^(SDLSystemCapability * _Nullable capability, BOOL subscribed, NSError * _Nullable error) {
    NSArray<SDLAppServiceCapability *> *appServices = capability.appServicesCapabilities.appServices;
    <#Use the app services records#>
}];
let subscribedObserver = sdlManager.systemCapabilityManager.subscribe(capabilityType: .appServices) { (capability, subscribed, error) in
    let appServices = capability?.appServicesCapabilities?.appServices
    <#Use the app services records#>
}

Checking the App Service Capability

Once you've retrieved the initial list of app service capabilities or an updated list of app service capabilities, you may want to inspect the data to find what you are looking for. Below is example code with comments explaining what each part of the app service capability is used for.

SDLAppServicesCapabilities *capabilities = systemCapabilityManager.appServicesCapabilities;

// This array contains all currently available app services on the system
NSArray<SDLAppServiceCapability *> *appServices = capabilities.appServices;
SDLAppServiceCapability *aCapability = appServices.firstObject;

// This will be nil since it's the first update
SDLServiceUpdateReason capabilityReason = aCapability.updateReason;

// The app service record will give you access to a service's generated id, which can be used to address the service directly (see below), it's manifest, used to see what data it supports, whether or not the service is published (it always will be here), and whether or not the service is the active service for its service type (only one service can be active for each type)
SDLAppServiceRecord *serviceRecord = aCapability.updatedAppServiceRecord;

// From OnSystemCapabilityUpdated
SDLOnSystemCapabilityUpdated *serviceNotification = <#From wherever you got it#>;
SDLAppServicesCapabilities *capabilities = serviceNotification.systemCapability.appServicesCapabilities;

// This array contains all recently updated services
NSArray<SDLAppServiceCapability *> *appServices = capabilities.appServices;
SDLAppServiceCapability *aCapability = appServices.firstObject;

// This won't be nil. It will tell you why a service is in the list of updates
SDLServiceUpdateReason capabilityReason = aCapability.updateReason;

// The app service record will give you access to a service's generated id, which can be used to address the service directly (see below), it's manifest, used to see what data it supports, whether or not the service is published (if it's not, it was just removed and should not be addressed), and whether or not the service is the active service for its service type (only one service can be active for each type)
SDLAppServiceRecord *serviceRecord = aCapability.updatedAppServiceRecord;
// This array contains all currently available app services on the system
guard let capabilities = systemCapabilityManager.appServicesCapabilities, let appServices = capabilities.appServices, let aCapability = appServices.first else {
    return
}

// This will be nil since it's the first update
let capabilityReason = aCapability.updateReason

// The app service record will give you access to a service's generated id, which can be used to address the service directly (see below), it's manifest, used to see what data it supports, whether or not the service is published (it always will be here), and whether or not the service is the active service for its service type (only one service can be active for each type)
let serviceRecord = aCapability.updatedAppServiceRecord

// From OnSystemCapabilityUpdated
let serviceNotification: SDLOnSystemCapabilityUpdated = <#From wherever you got it#>

// This array contains all recently updated services
guard let capabilities = serviceNotification.systemCapability.appServicesCapabilities, let appServices = capabilities.appServices, let aCapability = appServices.first  else {
    return
}

// This won't be nil. It will tell you why a service is in the list of updates
let capabilityReason = aCapability.updateReason

// The app service record will give you access to a service's generated id, which can be used to address the service directly (see below), it's manifest, used to see what data it supports, whether or not the service is published (if it's not, it was just removed and should not be addressed), and whether or not the service is the active service for its service type (only one service can be active for each type)
let serviceRecord = aCapability.updatedAppServiceRecord

2. Getting and Subscribing to a Service Type's Data

Once you have information about all of the services available, you may want to view or subscribe to a service type's data. To do so, you will use the GetAppServiceData RPC.

Note that you will currently only be able to get data for the active service of the service type. You can attempt to make another service the active service by using the PerformAppServiceInteraction RPC, discussed below in Sending an Action to a Service Provider.

// Get service data once
SDLGetAppServiceData *getServiceData = [[SDLGetAppServiceData alloc] initWithAppServiceType:SDLAppServiceTypeMedia];

// Subscribe to service data in perpetuity via `OnAppServiceData` notifications.
SDLGetAppServiceData *subscribeServiceData = [[SDLGetAppServiceData alloc] initAndSubscribeToAppServiceType:SDLAppServiceTypeMedia];

// Unsubscribe to service data previously subscribed
SDLGetAppServiceData *unsubscribeServiceData = [[SDLGetAppServiceData alloc] initWithAppServiceType:SDLAppServiceTypeMedia];
unsubscribeServiceData.subscribe = @NO;

// Get the service's data
[self.sdlManager sendRequest:getServiceData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
    if (!response || !response.success.boolValue) {
        SDLLogE(@"Error sending get system capability: Req %@, Res %@, err %@", request, response, error);
        return;
    }

    SDLGetAppServiceDataResponse *serviceResponse = (SDLGetAppServiceDataResponse *)response;
    SDLMediaServiceData *mediaData = serviceResponse.serviceData.mediaServiceData;

    <#Use the mediaData#>
}];
// Subscribe to service data in perpetuity via `OnAppServiceData` notifications.
let subscribeServiceData = SDLGetAppServiceData(andSubscribeToAppServiceType: .media)

// Unsubscribe to service data previously subscribed
let unsubscribeServiceData = SDLGetAppServiceData(appServiceType: .media)
unsubscribeServiceData.subscribe = NSNumber(false)

// Get the service's data
let getServiceData = SDLGetAppServiceData(appServiceType: .media)
sdlManager.send(request: getServiceData) { (req, res, err) in
    guard let response = res as? SDLGetAppServiceDataResponse, response.success.boolValue == true, let mediaData = response.serviceData?.mediaServiceData else { return }

    <#Use the mediaData#>
}

Interacting with a Service Provider

Once you have a service's data, you may want to interact with a service provider by sending RPCs or actions.

3. Sending RPCs to a Service Provider

Only certain RPCs are available to be passed to the service provider based on their service type. See the Creating an App Service guide Supporting Service RPCs and Actions section for a chart detailing which RPCs work with which service types. The RPC can only be sent to the active service of a specific service type, not to any inactive service.

Sending an RPC works exactly the same as if you were sending the RPC to the head unit system. The head unit will simply route your RPC to the appropriate app automatically.

Note

Your app may need special permissions to use the RPCs that route to app service providers.

SDLButtonPress *buttonPress = [[SDLButtonPress alloc] initWithButtonName:SDLButtonNameOk moduleType:SDLModuleTypeAudio moduleId:nil buttonPressMode:SDLButtonPressModeShort];
[self.sdlManager sendRequest:buttonPress withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
    if (!response || !response.success.boolValue) {
        SDLLogE(@"Error sending button press: Req %@, Res %@, err %@", request, response, error);
        return;
    }

    SDLButtonPressResponse *pressResponse = (SDLButtonPressResponse *)response;
    <#Use the response#>
}];
let buttonPress = SDLButtonPress(buttonName: .ok, moduleType: .audio, moduleId: nil, buttonPressMode: .short)
sdlManager.send(request: buttonPress) { (req, res, err) in
    guard let response = res as? SDLButtonPressResponse, response.success.boolValue == true else { return }

    <#Use the response#>
}

4. Sending an Action to a Service Provider

Actions are generic URI-based strings sent to any app service (active or not). You can also use actions to request to the system that they make the service the active service for that service type. Service actions are schema-less, i.e. there is no way to define the appropriate URIs through SDL. The service provider must document their list of available actions elsewhere (such as their website).

SDLPerformAppServiceInteraction *performAction = [[SDLPerformAppServiceInteraction alloc] initWithServiceUri:@"<#sdlexample://x-callback-url/showText?x-source=MyApp&text=My%20Custom%20String#>" serviceID:@"<#Previously Retrieved ServiceID#>" originApp:@"<#Your App Id#>" requestServiceActive:NO];
[self.sdlManager sendRequest:performAction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
    if (!response || !response.success.boolValue) {
        SDLLogE(@"Error sending perform action: Req %@, Res %@, err %@", request, response, error);
        return;
    }

    SDLPerformAppServiceInteractionResponse *actionResponse = (SDLPerformAppServiceInteractionResponse *)response;
    <#Use the response#>
}];
let performAction = SDLPerformAppServiceInteraction(serviceUri: "<#sdlexample://x-callback-url/showText?x-source=MyApp&text=My%20Custom%20String#>", serviceID: "<#Previously Retrieved ServiceID#>", originApp: "<#Your App Id#>", requestServiceActive: false)
sdlManager.send(request: performAction) { (req, res, err) in
    guard let response = res as? SDLPerformAppServiceInteractionResponse else { return }
    <#Check the error and response#>
}

5. Getting a File from a Service Provider

In some cases, a service may upload an image that can then be retrieved from the module. First, you will need to get the image name from the SDLAppServiceData (see point 2 above). Then you will use the image name to retrieve the image data.

SDLAppServiceData *data = <#Get the App Service Data#>;
SDLWeatherServiceData *weatherData = data.weatherServiceData;
SDLImage *currentForecastImage = weatherData.currentForecast.weatherIcon;
NSString *currentForecastImageName = currentForecastImage.value;
SDLGetFile *getCurrentForecastImage = [[SDLGetFile alloc] initWithFileName:currentForecastImageName];

__block NSUInteger imageDataLength = 0;
__block NSUInteger imageDataLengthReceived = 0;
NSMutableData *imageData = [[NSMutableData alloc] init];
[self.sdlManager sendRequest:getCurrentForecastImage withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
    SDLGetFileResponse *getFileResponse = response;
    if (getFileResponse == nil || !response.success) {
        // Something went wrong, examine the resultCode and info
        return;
    }

    NSData *rpcImageData = response.bulkData;
    if (imageData == nil) {
        // There's no image data
        return;
    }

    [imageData appendData:rpcImageData];
    imageDataLengthReceived += rpcImageData.length;
    if (getFileResponse.offset == 0 && getFileResponse.length != nil) {
        imageDataLength = getFileResponse.length.unsignedIntegerValue;
    }

    if (imageDataLengthReceived < imageDataLength) {
        // Send additional GetFile requests to get the rest of the data using the offset parameter
    } else {
        // The file is complete, turn the file data into an image and use it.
    }
}];
let data: SDLAppServiceData = <#Get the App Service Data#>
let weatherData = data.weatherServiceData
guard let currentForecastImage = weatherData?.currentForecast?.weatherIcon else {
    // The image doesn't exist, exit early
    return
}
let currentForecastImageName = currentForecastImage.value
let getCurrentForecastImage = SDLGetFile(fileName: currentForecastImageName)

var imageDataLength = 0
var imageDataLengthReceived = 0
var imageData = Data()
sdlManager.send(request: getCurrentForecastImage) { (req, res, err) in
    guard let response = res as? SDLGetFileResponse, response.success.boolValue == true, let rpcImageData = response.bulkData else {
        // Something went wrong, examine the resultCode and info
        return;
    }

    imageData.append(rpcImageData)
    imageDataLengthReceived += rpcImageData.count
    if response.offset?.intValue == 0, let rpcImageLength = response.length?.intValue {
        imageDataLength = rpcImageLength
    }

    if imageDataLengthReceived < imageDataLength {
        // Send additional GetFile requests to get the rest of the data using the offset parameter
    }
}
View on GitHub.com
Previous Section Next Section