While creating your SDL app, remember that just because your app is connected to a head unit it does not mean that the app has permission to send the RPCs you want. If your app does not have the required permissions, requests will be rejected. There are three important things to remember in regards to permissions:
hmiLevel
s during which it can be sent.When your app is connected to the head unit you will receive notifications when the SDL app's HMI status changes. Your app can be in one of four different hmiLevel
s:
HMI Level | What does this mean? |
---|---|
NONE | The user has not yet opened your app, or the app has been killed. |
BACKGROUND | The user has opened your app, but is currently in another part of the head unit. |
LIMITED | This level only applies to media and navigation apps (i.e. apps with an appType of MEDIA or NAVIGATION ). The user has opened your app, but is currently in another part of the head unit. The app can receive button presses from the play, seek, tune, and preset buttons. |
FULL | Your app is currently in focus on the screen. |
Be careful with sending user interface related RPCs in the NONE
and BACKGROUND
levels; some head units may reject RPCs sent in those states. We recommended that you wait until your app's hmiLevel
enters FULL
to set up your app's UI.
To get more detailed information about the state of your SDL app check the current system context. The system context will let you know if a menu is open, a VR session is in progress, an alert is showing, or if the main screen is unobstructed. You can find more information about the system context below.
The easiest way to monitor the hmiLevel
of your SDL app is through a required delegate callback of SDLManagerDelegate
. The function hmiLevel:didChangeToLevel:
is called every time your app's hmiLevel
changes.
- (void)hmiLevel:(SDLHMILevel)oldLevel didChangeToLevel:(SDLHMILevel)newLevel { if (![newLevel isEqualToEnum:SDLHMILevelNone] && (self.firstHMILevel == SDLHMILevelNone)) { // This is our first time in a non-`NONE` state self.firstHMILevel = newLevel; <#Send static menu RPCs#> } if ([newLevel isEqualToEnum:SDLHMILevelFull]) { <#Send user interface RPCs#> } else if ([newLevel isEqualToEnum:SDLHMILevelLimited]) { <#Code#> } else if ([newLevel isEqualToEnum:SDLHMILevelBackground]) { <#Code#> } else if ([newLevel isEqualToEnum:SDLHMILevelNone]) { <#Code#> } }
fileprivate var firstHMILevel: SDLHMILevel = .none func hmiLevel(_ oldLevel: SDLHMILevel, didChangeToLevel newLevel: SDLHMILevel) { if newLevel != .none && firstHMILevel == .none { // This is our first time in a non-`NONE` state firstHMILevel = newLevel <#Send static menu RPCs#> } switch newLevel { case .full: <#Send user interface RPCs#> case .limited: break case .background: break case .none: break default: break } }
The PermissionManager allows developers to easily query whether specific RPCs are allowed or not in the current state of the app. It also allows a listener to be added for RPCs or their parameters so that if there are changes in their permissions, the app will be notified.
BOOL isAllowed = [self.sdlManager.permissionManager isRPCNameAllowed:<#RPC name#>];
let isAllowed = sdlManager.permissionManager.isRPCNameAllowed(<#RPC name#>)
You can also retrieve the status of a group of RPCs. First, you can retrieve the permission status of the group of RPCs as a whole: whether or not those RPCs are all allowed, all disallowed, or some are allowed and some are disallowed. This will allow you to know, for example, if a feature you need is allowed based on the status of all the RPCs needed for the feature.
SDLPermissionElement *showElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameShow parameterPermissions:nil]; SDLPermissionElement *getVehicleDataElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameGetVehicleData parameterPermissions:@[@"rpm"]]; SDLPermissionGroupStatus groupStatus = [self.sdlManager.permissionManager groupStatusOfRPCPermissions:@[showElement, getVehicleDataElement]]; switch (groupStatus) { case SDLPermissionGroupStatusAllowed: // All of the RPCs are allowed break; case SDLPermissionGroupStatusMixed: // Some are allowed, others are disallowed break; case SDLPermissionGroupStatusDisallowed: // All of the RPCs are disallowed break; case SDLPermissionGroupStatusUnknown: // We don't yet know the status of the RPCs break; }
let showElement = SDLPermissionElement(rpcName: .show, parameterPermissions: nil) let getVehicleDataElement = SDLPermissionElement(rpcName: .getVehicleData, parameterPermissions: ["rpm"]) let groupStatus = sdlManager.permissionManager.groupStatus(ofRPCPermissions:[showElement, getVehicleDataElement]) switch groupStatus { case .allowed: // All of the RPCs are allowed break; case .mixed: // Some are allowed, others are disallowed break; case .disallowed: // All of the RPCs are disallowed break; case .unknown: // We don't yet know the status of the RPCs break; }
The previous snippet will give a quick generic status for all permissions together. However, if you want to get a more detailed result about the status of every permission or parameter in the group, you can use the statusesOfRPCPermissions:
method.
SDLPermissionElement *showElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameShow parameterPermissions:nil]; SDLPermissionElement *getVehicleDataElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameGetVehicleData parameterPermissions:@[@"rpm"]]; NSDictionary<SDLRPCFunctionName, SDLRPCPermissionStatus *> *status = [self.sdlManager.permissionManager statusesOfRPCPermissions:@[showElement, getVehicleDataElement]]; if (status[SDLRPCFunctionNameGetVehicleData].isRPCAllowed) { // GetVehicleData RPC is allowed } if (status[SDLRPCFunctionNameGetVehicleData].rpcParameters[@"rpm"].boolValue) { // RPM parameter in GetVehicleDataRPC is allowed }
let showElement = SDLPermissionElement(rpcName: .show, parameterPermissions: nil) let getVehicleDataElement = SDLPermissionElement(rpcName: .getVehicleData, parameterPermissions: ["rpm"]) let status = sdlManager.permissionManager.statuses(ofRPCPermissions:[showElement, getVehicleDataElement]) if status[.getVehicleData]?.isRPCAllowed == true { // GetVehicleData RPC is allowed } if status[.getVehicleData]?.rpcParameters?["rpm"]?.boolValue == true { // RPM parameter in GetVehicleDataRPC is allowed }
If desired, you can subscribe to a group of permissions. The subscription's handler will be called when the permissions for the group changes. If you want to be notified when the permission status of any of RPCs in the group change, set the groupType
to SDLPermissionGroupTypeAny
. If you only want to be notified when all of the RPCs in the group are allowed, or go from allowed to some/all not allowed, set the groupType
to SDLPermissionGroupTypeAllAllowed
.
SDLPermissionElement *showElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameShow parameterPermissions:nil]; SDLPermissionElement *getVehicleDataElement = [[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameGetVehicleData parameterPermissions:@[@"rpm"]]; SDLPermissionObserverIdentifier subscriptionId = [self.sdlManager.permissionManager subscribeToRPCPermissions:@[showElement, getVehicleDataElement] groupType:<#SDLPermissionGroupType#> withHandler:^(NSDictionary<SDLRPCFunctionName, SDLRPCPermissionStatus *> *_Nonnull updatedPermissionStatuses, SDLPermissionGroupStatus updatedGroupStatus) { if (updatedPermissionStatuses[SDLRPCFunctionNameGetVehicleData].isRPCAllowed) { // GetVehicleData RPC is allowed } if (updatedPermissionStatuses[SDLRPCFunctionNameGetVehicleData].rpcParameters[@"rpm"].boolValue) { // RPM parameter in GetVehicleDataRPC is allowed } }];
let showElement = SDLPermissionElement(rpcName: .show, parameterPermissions: nil) let getVehicleDataElement = SDLPermissionElement(rpcName: .getVehicleData, parameterPermissions: ["rpm", "airbagStatus"]) let subscriptionId = sdlManager.permissionManager.subscribe(toRPCPermissions: [showElement, getVehicleDataElement], groupType: .allAllowed) { (updatedPermissionStatuses, updatedGroupStatus) in if updatedPermissionStatuses[.getVehicleData]?.isRPCAllowed == true { // GetVehicleData RPC is allowed } if updatedPermissionStatuses[.getVehicleData]?.rpcParameters?["rpm"]?.boolValue == true { // RPM parameter in GetVehicleDataRPC is allowed } }
When you set up the subscription, you will get a unique id back. Use this id to unsubscribe to the permissions at a later date.
[self.sdlManager.permissionManager removeObserverForIdentifier:observerId];
sdlManager.permissionManager.removeObserver(forIdentifier: observerId)
If you want more detail about the current state of your SDL app you can monitor the audio playback state as well as get notifications when something blocks the main screen of your app.
The Audio Streaming State informs your app whether or not the driver will be able to hear your app's audio. It will be either AUDIBLE
, NOT_AUDIBLE
, or ATTENUATED
.
You will get these notifications when an alert pops up, when you start recording the in-car audio, when voice recognition is active, when another app takes audio control, when a navigation app is giving directions, etc.
Audio Streaming State | What does this mean? |
---|---|
AUDIBLE | Any audio you are playing will be audible to the user |
ATTENUATED | Some kind of audio mixing is occurring between what you are playing, if anything, and some system level audio or navigation application audio. |
NOT_AUDIBLE | Your streaming audio is not audible. This could occur during a VRSESSION System Context. |
- (void)audioStreamingState:(nullable SDLAudioStreamingState)oldState didChangeToState:(SDLAudioStreamingState)newState { <#code#> }
func audioStreamingState(_ oldState: SDLAudioStreamingState?, didChangeToState newState: SDLAudioStreamingState) { <#code#> }
The System Context informs your app if there is potentially a blocking HMI component while your app is still visible. An example of this would be if your application is open and you display an alert. Your app will receive a system context of ALERT
while it is presented on the screen, followed by MAIN
when it is dismissed.
System Context State | What does this mean? |
---|---|
MAIN | No user interaction is in progress that could be blocking your app's visibility. |
VRSESSION | Voice recognition is currently in progress. |
MENU | A menu interaction is currently in-progress. |
HMI_OBSCURED | The app's display HMI is being blocked by either a system or other app's overlay (another app's alert, for instance). |
ALERT | An alert that you have sent is currently visible. |
- (void)systemContext:(nullable SDLSystemContext)oldContext didChangeToContext:(SDLSystemContext)newContext { <#code#> }
func systemContext(_ oldContext: SDLSystemContext?, didChangeToContext newContext: SDLSystemContext) { <#code#> }