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
Android Guides
Integration Basics - Java

Integration Basics

In this guide, we exclusively use Android Studio. We are going to set-up a bare-bones application so you get started using SDL.

Note

The SDL Mobile library supports Android 4.1 (API Level 16) or higher.

A SmartDeviceLink Service should be created to manage the lifecycle of the SDL session. The SdlService should build and start an instance of the SdlManager which will automatically connect with a head unit when available. This SdlManager will handle sending and receiving messages to and from SDL after it is connected.

Note

Please be aware that using an Activity to host the SDL implementation will not work. Android 10 has restrictions on starting activities from the background and that is how the SDL library will start the supplied component. SDL apps should only use a foreground service to host the SDL implementation.

Create a new service and name it appropriately, for this guide we are going to call it SdlService.

public class SdlService extends Service {
    //...
}

If you created the service using the Android Studio template then the service should have been added to your AndroidManifest.xml. If not, then service needs to be defined in the manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.company.mySdlApplication">

    <application>

        <service
        android:name=".SdlService"
        android:exported="true"
        android:enabled="true"
        android:foregroundServiceType='connectedDevice'/>

    </application>

</manifest>
Note

Android API 31 now requires any Service that will be started from an external source to explicitly set the exported flag to true (exported=true).
You can set this flag if you wish to allow the active Router Service to start your SdlService while your app is in the background.

If you do not wish to set this flag for your SdlService you can still start your SdlService while your app is in the foreground.

Entering the Foreground

Because of Android Oreo's requirements, it is mandatory that services enter the foreground for long running tasks. The first bit of integration is ensuring that happens in the onCreate method of the SdlService or similar. Within the service that implements the SDL lifecycle you will need to add a call to start the service in the foreground. This will include creating a notification to sit in the status bar tray. This information and icons should be relevant for what the service is doing/going to do. If you already start your service in the foreground, you can ignore this section.

@Override
public void onCreate() {
    super.onCreate();
    //...
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        try {
            NotificationChannel channel = new NotificationChannel("channelId", "channelName", NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            if (notificationManager != null) {
                notificationManager.createNotificationChannel(channel);
                Notification serviceNotification = new Notification.Builder(this, channel.getId())
                        .setContentTitle(...)
                        .setSmallIcon(...)
                        .setContentText(...)
                        .setChannelId(channel.getId())
                        .build();
                startForeground(FOREGROUND_SERVICE_ID, serviceNotification);
            }
        } catch (Exception e) {
            // This should only occur when using TCP connections on Android 14+ due to needing
            // specific connected devices for permissions regarding ForegroundServiceType 
            // ConnectedDevice where a TCP connection doesn't apply
            DebugTool.logError(TAG, "Unable to start service in foreground", e);
        }
    }
}
Note

The sample code checks if the OS is of Android Oreo or newer to start a foreground service. It is up to the app developer if they wish to start the notification in previous versions.

Exiting the Foreground

It's important that you don't leave your notification in the notification tray as it is very confusing to users. So in the onDestroy method in your service, simply call the stopForeground method.

@Override
public void onDestroy(){
    //...
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if(notificationManager!=null){ //If this is the only notification on your channel
            notificationManager.deleteNotificationChannel(* Notification Channel*);
        }
        stopForeground(true);
    }
}

Implementing SDL Manager

In order to correctly connect to an SDL enabled head unit developers need to implement methods for the proper creation and disposing of an SdlManager in our SdlService.

Note

An instance of SdlManager cannot be reused after it is closed and properly disposed of. Instead, a new instance must be created. Only one instance of SdlManager should be in use at any given time.

Must

SdlManagerListener method: onSystemInfoReceived auto generates in Android Studio to returns false. This will cause your app to not connect. You must change it to true or implement logic to check system info to see if you wish for your app to connect to that system.

public class SdlService extends Service {

    //The manager handles communication between the application and SDL
    private SdlManager sdlManager = null;

    //...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (sdlManager == null) {
            MultiplexTransportConfig transport = new MultiplexTransportConfig(this, APP_ID, MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF);

            // The app type to be used
            Vector<AppHMIType> appType = new Vector<>();
            appType.add(AppHMIType.MEDIA);

            // The manager listener helps you know when certain events that pertain to the SDL Manager happen
            SdlManagerListener listener = new SdlManagerListener() {

                @Override
                public void onStart() {
                    // After this callback is triggered the SdlManager can be used to interact with the connected SDL session (updating the display, sending RPCs, etc)
                }

                @Override
                public void onDestroy() {
                    SdlService.this.stopSelf();
                }

                @Override
                public void onError(String info, Exception e) {
                }

                @Override
                public LifecycleConfigurationUpdate managerShouldUpdateLifecycle(Language language, Language hmiLanguage) {
                    return null;
                }

                @Override
                public boolean onSystemInfoReceived(SystemInfo systemInfo) {
                    // Check the SystemInfo object to ensure that the connection to the device should continue
                    return true;
                }
            };

            // Create App Icon, this is set in the SdlManager builder
            SdlArtwork appIcon = new SdlArtwork(ICON_FILENAME, FileType.GRAPHIC_PNG, R.mipmap.ic_launcher, true);

            // The manager builder sets options for your session
            SdlManager.Builder builder = new SdlManager.Builder(this, APP_ID, APP_NAME, listener);
            builder.setAppTypes(appType);
            builder.setTransportType(transport);
            builder.setAppIcon(appIcon);
            sdlManager = builder.build();
            sdlManager.start();
        }

        return START_STICKY;
    }
}

The onDestroy() method from the SdlManagerListener is called whenever the manager detects some disconnect in the connection, whether initiated by the app, by SDL, or by the device’s connection.

Must

The sdlManager must be shutdown properly in the SdlService.onDestroy() callback using the method sdlManager.dispose().

Optional SdlManager Builder Parameters

App Icon

This is a custom icon for your application. Please refer to Adaptive Interface Capabilities for icon sizes.

builder.setAppIcon(appIcon);
App Type

The app type is used by car manufacturers to decide how to categorize your app. Each car manufacturer has a different categorization system. For example, if you set your app type as media, your app will also show up in the audio tab as well as the apps tab of Ford’s SYNC® 3 head unit. The app type options are: default, communication, media (i.e. music/podcasts/radio), messaging, navigation, projection, information, and social.

Vector<AppHMIType> appHMITypes = new Vector<>();
appHMITypes.add(AppHMIType.MEDIA);

builder.setAppTypes(appHMITypes);
Note

Navigation and projection applications both use video and audio byte streaming. However, navigation apps require special permissions from OEMs, and projection apps are only for internal use by OEMs.

Short App Name

This is a shortened version of your app name that is substituted when the full app name will not be visible due to character count constraints. You will want to make this as short as possible.

builder.setShortAppName(shortAppName);
Template Coloring

You can customize the color scheme of your initial template on head units that support this feature using the builder. For more information, see the Customizing the Template guide section.

Lock Screen Configuration

A lock screen is used to prevent the user from interacting with the app on the smartphone while they are driving. When the vehicle starts moving, the lock screen is activated. Similarly, when the vehicle stops moving, the lock screen is removed. You must implement a lock screen in your app for safety reasons. Any application without a lock screen will not get approval for release to the public.

The SDL SDK can take care of the lock screen implementation for you, automatically using your app logo and the connected vehicle logo. If you do not want to use the default lock screen, you can implement your own custom lock screen.

LockScreenConfig lockScreenConfig = new LockScreenConfig();
builder.setLockScreenConfig(lockScreenConfig);

You should also declare the SDLLockScreenActivity in your manifest. For more information, please refer to the Adding the Lock Screen section.

SdlSecurity

Some OEMs may want to encrypt messages passed between your SDL app and the head unit. If this is the case, when you submit your app to the OEM for review, they will ask you to add a security library to your SDL app. See the Encryption section.

File Manager Configuration

The file manager configuration allows you to configure retry behavior for uploading files and images. The default configuration attempts one re-upload, but will fail after that.

FileManagerConfig fileManagerConfig = new FileManagerConfig();
fileManagerConfig.setArtworkRetryCount(2);
fileManagerConfig.setFileRetryCount(2);

builder.setFileManagerConfig(fileManagerConfig);
Language

The desired language to be used on display/HMI of connected module can be set.

builder.setLanguage(Language.EN_US);
Listening for RPC notifications and events

You can listen for specific events using SdlManager's builder setRPCNotificationListeners. The following example shows how to listen for HMI Status notifications. Additional listeners can be added for specific RPCs by using their corresponding FunctionID in place of the ON_HMI_STATUS in the following example and casting the RPCNotification object to the correct type.

Map<FunctionID, OnRPCNotificationListener> onRPCNotificationListenerMap = new HashMap<>();
onRPCNotificationListenerMap.put(FunctionID.ON_HMI_STATUS, new OnRPCNotificationListener() {
    @Override
    public void onNotified(RPCNotification notification) {
        OnHMIStatus onHMIStatus = (OnHMIStatus) notification;
        if (onHMIStatus.getHmiLevel() == HMILevel.HMI_FULL && onHMIStatus.getFirstRun()){
            // first time in HMI Full
        }
    }
});
builder.setRPCNotificationListeners(onRPCNotificationListenerMap);

You can also use addOnRPCNotificationListener when creating an SdlManagerListener object. The following example shows how to set up the listener in the onStart() method of an SdlManagerListener object.

@Override
public void onStart() {
    // HMI Status Listener
    sdlManager.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, new OnRPCNotificationListener() {
        @Override
        public void onNotified(RPCNotification notification) {
            OnHMIStatus onHMIStatus = (OnHMIStatus) notification;
            if (onHMIStatus.getWindowID() != null && onHMIStatus.getWindowID() != PredefinedWindows.DEFAULT_WINDOW.getValue()) {
                return;
            }
            if (onHMIStatus.getHmiLevel() == HMILevel.HMI_FULL && onHMIStatus.getFirstRun()) {
                // first time in HMI Full
            }
        }
    });
}
Hash Resumptions

Set a hashID for your application that can be used over connection cycles (i.e. loss of connection, ignition cycles, etc.).

builder.setResumeHash(hashID);

Determining SDL Support

You have the ability to determine a minimum SDL protocol and a minimum SDL RPC version that your app supports. You can also check the connected vehicle type and disconnect if the vehicle module is not supported. We recommend not setting these values until your app is ready for production. The OEMs you support will help you configure correct values during the application review process.

Blocking By Version

If a head unit is blocked by protocol version, your app icon will never appear on the head unit's screen. If you configure your app to block by RPC version, it will appear and then quickly disappear. So while blocking with minimumProtocolVersion is preferable, minimumRPCVersion allows you more granular control over which RPCs will be present.

builder.setMinimumProtocolVersion(new Version("3.0.0"));
builder.setMinimumRPCVersion(new Version("4.0.0"));
Blocking By Vehicle Type

If you are blocking by vehicle type and you are connected over RPC v7.1+, your app icon will never appear on the head unit's screen. If you are connected over RPC v7.0 or below, it will appear and then quickly disappear. To implement this type of blocking, you need to set up the SDLManagerListener. You will then implement logic in onSystemInfoReceived method and return true if you want to continue the connection and false if you wish to disconnect.

The SdlRouterService will listen for a connection with an SDL enabled module. When a connection happens, it will alert all SDL enabled apps that a connection has been established and they should start their SDL services.

We must implement a local copy of the SdlRouterService into our project. The class doesn't need any modification, it's just important that we include it. We will extend the com.smartdevicelink.transport.SdlRouterService in our class named SdlRouterService:

Note

Do not include an import for com.smartdevicelink.transport.SdlRouterService. Otherwise, we will get an error for 'SdlRouterService' is already defined in this compilation unit.

public class SdlRouterService extends  com.smartdevicelink.transport.SdlRouterService {
    //Nothing to do here
}
Must

The local extension of the com.smartdevicelink.transport.SdlRouterService must be named SdlRouterService.

Must

Make sure this local class SdlRouterService.java is in the same package of SdlReceiver.java (described below)

If you created the service using the Android Studio template then the service should have been added to your AndroidManifest.xml otherwise the service needs to be added in the manifest. Because we want our service to be seen by other SDL enabled apps, we need to set android:exported="true". The system may issue a lint warning because of this, so we can suppress that using tools:ignore="ExportedService".

Note

Android API 29 adds a new attribute foregroundServiceType to specify the type of foreground service.
Starting with Android API 29 please include android:foregroundServiceType='connectedDevice' to the service tag for SdlRouterService in your AndroidManifest.xml

Must

The SdlRouterService must be placed in a separate process with the name com.smartdevicelink.router. If it is not in that process during its start up it will stop itself.

Intent Filter

<intent-filter>
    <action android:name="com.smartdevicelink.router.service"/>
</intent-filter>

The new versions of the SDL Android library rely on the com.smartdevicelink.router.service action to query SDL enabled apps that host router services. This allows the library to determine which router service to start.

Must

This intent-filter MUST be included.

Metadata

Router Service Version

<meta-data android:name="sdl_router_version"  android:value="@integer/sdl_router_service_version_value" />

Adding the sdl_router_version metadata allows the library to know the version of the router service that the app is using. This makes it simpler for the library to choose the newest router service when multiple router services are available.

Custom Router Service

<meta-data android:name="sdl_custom_router" android:value="false" />
Note

This is only for specific OEM applications, therefore normal developers do not need to worry about this.

Some OEMs choose to implement custom router services. Setting the sdl_custom_router metadata value to true means that the app is using something custom over the default router service that is included in the SDL Android library. Do not include this meta-data entry unless you know what you are doing.

The final router service entry in the AndroidManifest.xml file should look like the following:

<service
    android:name=".SdlRouterService"
    android:enabled="true"
    android:exported="true"
    android:foregroundServiceType="connectedDevice"
    android:process="com.smartdevicelink.router">

    <intent-filter>
        <action android:name="com.smartdevicelink.router.service" />
    </intent-filter>

    <meta-data
        android:name="sdl_router_version"
        android:value="@integer/sdl_router_service_version_value" />
</service>

The Android implementation of the SdlManager relies heavily on the OS's bluetooth and USB intents. When the phone is connected to SDL and the router service has sent a connection intent, the app needs to create an SdlManager, which will bind to the already connected router service. As mentioned previously, the SdlManager cannot be re-used. When a disconnect between the app and SDL occurs, the current SdlManager must be disposed of and a new one created.

The SDL Android library has a custom broadcast receiver named SdlBroadcastReceiver that should be used as the base for your BroadcastReceiver. It is a child class of Android's BroadcastReceiver so all normal flow and attributes will be available. Two abstract methods will be automatically populate the class, we will fill them out soon.

Create a new SdlBroadcastReceiver and name it appropriately, for this guide we are going to call it SdlReceiver:

public class SdlReceiver extends SdlBroadcastReceiver {

    @Override
    public void onSdlEnabled(Context context, Intent intent) {
        //...

    }

    @Override
    public Class<? extends SdlRouterService> defineLocalSdlRouterClass() {
        //...
    }
}
Must

SdlBroadcastReceiver must call super if onReceive is overridden

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        //your code here
    }

If you created the BroadcastReceiver using the Android Studio template then the service should have been added to your AndroidManifest.xml otherwise the receiver needs to be defined in the manifest. Regardless, the manifest needs to be edited so that the SdlBroadcastReceiver needs to respond to the following intents:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.company.mySdlApplication">

    <application>

        <receiver
            android:name=".SdlReceiver"
            android:exported="true"
            android:enabled="true">

            <intent-filter>
                <action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
                <action android:name="sdl.router.startservice" />
            </intent-filter>

        </receiver>

    </application>

</manifest>
Note

The intent sdl.router.startservice is a custom intent that will come from the SdlRouterService to tell us that we have just connected to an SDL enabled piece of hardware.

Must

SdlBroadcastReceiver has to be exported, or it will not work correctly

Next, we want to make sure we supply our instance of the SdlBroadcastService with our local copy of the SdlRouterService. We do this by simply returning the class object in the method defineLocalSdlRouterClass:

public class SdlReceiver extends SdlBroadcastReceiver {
   @Override
    public void onSdlEnabled(Context context, Intent intent) {

    }

    @Override
    public Class<? extends SdlRouterService> defineLocalSdlRouterClass() {
        //Return a local copy of the SdlRouterService located in your project
        return com.company.mySdlApplication.SdlRouterService.class;
    }
}

Starting SdlService

We want to start your SdlService when an SDL connection is made via the SdlRouterService. We do this by taking action in the onSdlEnabled method. Depending on which API levels your application supports, there are up to four ways that you may need to add logic for starting your service:

Android UPSIDE_DOWN_CAKE and greater

PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
if (pendingIntent != null) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
        if (!AndroidTools.hasForegroundServiceTypePermission(context)) {
            DebugTool.logInfo(TAG, "Permission missing for ForegroundServiceType connected device." + context);
            return;
        }
    }
    try {
        pendingIntent.send(context, 0, intent);
    } catch (PendingIntent.CanceledException e) {
        e.printStackTrace();
    }
}

Android S and greater

if (intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA) != null) {
    PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
    try {
        pendingIntent.send(context, 0, intent);
    } catch (PendingIntent.CanceledException e) {
        e.printStackTrace();
    }
}

Android O and greater, but less than S

context.startForegroundService(intent);

All versions less than Android O

context.startService(intent);

The example below shows logic for starting your service that supports all Android versions:

public class SdlReceiver extends SdlBroadcastReceiver {

   @Override
    public void onSdlEnabled(Context context, Intent intent) {
        DebugTool.logInfo(TAG, "SDL Enabled");
        intent.setClass(context, SdlService.class);

        // Starting with Android S SdlService needs to be started from a foreground context.
        // We will check the intent for a pendingIntent parcelable extra
        // This pendingIntent allows us to start the SdlService from the context of the active router service which is in the foreground
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent pendingIntent = (PendingIntent) intent.getParcelableExtra(TransportConstants.PENDING_INTENT_EXTRA);
            if (pendingIntent != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                    if (!AndroidTools.hasForegroundServiceTypePermission(context)) {
                        DebugTool.logInfo(TAG, "Permission missing for ForegroundServiceType connected device." + context);
                        return;
                    }
                }
                try {
                    pendingIntent.send(context, 0, intent);
                } catch (PendingIntent.CanceledException e) {
                    e.printStackTrace();
                }
            }
        } else {
            // SdlService needs to be foregrounded in Android O and above
            // This will prevent apps in the background from crashing when they try to start SdlService
            // Because Android O doesn't allow background apps to start background services
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(intent);
            } else {
                context.startService(intent);
            }
        }
    }

    @Override
    public Class<? extends SdlRouterService> defineLocalSdlRouterClass() {
        //Return a local copy of the SdlRouterService located in your project
        return com.company.mySdlApplication.SdlRouterService.class;
    }
}
Must

Apps must start their service in the foreground as of Android API 26, and as of Android API 31, the service must be started from a foreground context.

Either you will need to ensure the app is in the foreground when you start the SdlService or you will need to start the SdlService from a foreground context.

The intent received in onSdlEnabled will have a PendingIntent extra that will allow you start the SdlService from the context of the active SdlRouterService.

Note

The onSdlEnabled method will be the main start point for our SDL connection session. We define exactly what we want to happen when we find out we are connected to SDL enabled hardware.

There is now an overridable method, getSdlServiceName in the SdlBroadcastReceiver class. This method is used by the SdlBroadcastReceiver to catch possible foreground exceptions.

When the app tries to start the SdlService, if the service does not enter the foreground within a set amount of time (this time is designated by the Android operating system) an exception will be thrown and the app may encounter an ANR.

The SdlBroadcasterReceiver can catch this exception and prevent the ANR but will need to know the name of the class that throws the exception.

By default the getSdlServiceName method will return "SdlService". If your app uses a name other than "SdlService" you will need to override getSdlServiceName in the SdlReceiver class to return the correct name.

//...
@Override
public String getSdlServiceName() {
    return SDL_SERVICE_CLASS_NAME;
}
//...

Main Activity

Now that the basic connection infrastructure is in place, we should add methods to start the SdlService when our application starts. In onCreate() in your main activity, you need to call a method that will check to see if there is currently an SDL connection made. If there is one, the onSdlEnabled method will be called and we will follow the flow we already set up:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //If we are connected to a module we want to start our SdlService
        SdlReceiver.queryForConnectedService(this);
    }
}

Where to Go From Here

You should now be able to connect to a head unit or emulator. For more guidance on connecting, see Connecting to an Infotainment System. To start building your app, learn about designing your interface. Please also review the best practices for building an SDL app.

View on GitHub.com
Previous Section Next Section