Create a Custom Alexa Handler Service

Additional requirements exist for using Amazon Alexa in your TomTom IndiGO product.

The TomTom IndiGO platform offers a stock implementation of a VPA Adaptation service for the Alexa Auto VPA, provided by the stock Alexa service. This service interacts with the Alexa engine by exchanging asynchronous JSON-based messages with the Alexa Auto Client Service (AACS) application. More information about the AACS is available in the Alexa Auto Client Service (AACS) documentation page.

These JSON messages are called "AASB" messages in the Alexa Auto documentation, because they are used by the Alexa Auto Services Bridge (AASB) Extension.

The AASB messages are grouped into "topics", each representing a domain that requires a platform implementation. For details about the AASB messages, see the AASB message interfaces and the AASB message definitions pages.

The stock Alexa service of TomTom IndiGO already implements message handlers for many of the topics offered by Alexa Auto, namely:

  • AddressBook
  • Alerts
  • AlexaClient
  • AudioInput
  • AudioOutput
  • Authorization
  • CarControl
  • Messaging
  • Navigation
  • Notifications
  • PhoneCallController
  • PlaybackController
  • PropertyManager
  • SpeechRecognizer
  • TemplateRuntime

The responsibility of a message handler is:

  • To parse AASB messages received by AACS and handle them appropriately (for example, to instruct the navigation engine to start navigation when a StartNavigation message is received).
  • To send "Reply" AASB messages to AACS, reporting whether a request from AACS was successfully executed or not.
  • To send "Publish" AASB messages to AACS, for example, to send a request to Alexa or to report an event.

Some topics, though, are device-specific and cannot be implemented as part of the TomTom IndiGO platform. The AlexaHandlerService is a discoverable IVI service interface that can be implemented by an OEM to fulfill one of these purposes:

  • handle topics that are not supported by the TomTom IndiGO platform.
  • extend or replace some of the existing stock TomTom IndiGO platform message handlers. Currently, the only stock TomTom IndiGO platform message handlers that can be extended or replaced with an AlexaHandlerService implementation are the CarControl and the Navigation handlers. For example, you might want to add support for switching the Cabin Light in the vehicle on and off: this is a setting that Alexa Auto supports, but for which the TomTom IndiGO platform's CarControl message handler doesn't have built-in support. You could then add this functionality by implementing an AlexaHandlerService.

Example app

The TomTom IndiGO SDK comes with an example app showing how to use the Alexa feature, see directory examples/alexa. By default, this application is excluded from the build, because it requires Alexa libraries that are not publicly available.

To build the Alexa example app, you should edit the build files:

gradle.properties

optInToAlexaExamples=true

Alternatively, you can set this Gradle property from the command-line:

./gradlew assemble -PoptInToAlexaExamples=true

How to create a custom Alexa Handler service

The following sections describe how to create a custom Alexa Handler service implementation.

Create a service implementation module

To implement a custom Alexa Handler service, create a new module under examples/alexa (for example examples/alexa/customalexahandler) and add a Gradle build script.

build.gradle.kts

1import com.tomtom.ivi.platform.gradle.api.framework.config.ivi
2
3plugins {
4 `kotlinx-serialization`
5}
6
7ivi {
8 // The AlexaHandlerService API is currently an experimental feature, and has to be explicitly
9 // opted in.
10 optInToExperimentalApis = true
11}
12
13dependencies {
14 implementation(libraries.indigoPlatformAlexaApiCommonUtil)
15 implementation(libraries.indigoPlatformAlexaApiServiceAlexahandler)
16}

Declare Alexa dependencies in /build-logic/libraries.versions.toml file:

indigoPlatformAlexaApiCommonUtil = { module = "com.tomtom.ivi.platform:platform_alexa_api_common_util", version.ref = "indigoPlatform" }
indigoPlatformAlexaApiServiceAlexahandler = { module = "com.tomtom.ivi.platform:platform_alexa_api_service_alexahandler", version.ref = "indigoPlatform" }

The Alexa Handler service implementation project is an Android project, so it must also have an AndroidManifest.xml file.

src/main/AndroidManifest.xml

1<?xml version="1.0" encoding="utf-8"?>
2
3<manifest package="com.example.ivi.example.alexa.customalexahandler" />

Implement the Alexa Handler service

The Alexa Handler service can be implemented by defining a class (for example, CustomAlexaHandlerService) that inherits from the abstract AlexaHandlerServiceBase base class and implements the methods defined in the AlexaHandlerService interface.

The service implementation needs to set a number of properties for configuring itself with the TomTom IndiGO platform. Please refer to the AlexaHandlerService API reference documentation for detailed information on these properties.

The properties can be set by overriding the onCreate() method. For example:

src/main/kotlin/com/example/ivi/example/alexa/customalexahandler/CustomAlexaHandlerService.kt

1import com.amazon.aacsconstants.Topic
2import com.tomtom.ivi.platform.alexa.api.service.alexahandler.AlexaHandlerService
3
4override fun onCreate() {
5 super.onCreate()
6
7 topic = Topic.CAR_CONTROL
8 priority = AlexaHandlerService.DEFAULT_HANDLER_PRIORITY
9 aacsConfiguration =
10 """
11 {
12 "aacs.carControl" : {
13 "endpoints": [
14 ...
15 ]
16 }
17 }
18 """
19
20 serviceReady = true
21}

Additionally, the service needs to override a number of methods. Please refer to the AlexaHandlerService API reference documentation for detailed information on these methods.

Parsing AASB messages

The Alexa Handler service is able to communicate with the Alexa Auto Client Service by exchanging JSON based Alexa Auto Services Bridge (AASB) messages. More information on the format of the AASB messages is available in the Alexa Auto AASB message definitions page.

Whenever the TomTom IndiGO platform receives a JSON AASB message from the Alexa Auto Client Service which matches with the topic configured in the service implementation, the AlexaHandlerService.onMessageReceived method is called.

The service implementation needs to parse this message and handle it as needed. Typically, in order to parse an AASB message, you would:

  1. Instantiate an instance of the kotlinx-serialization JSON parser:

    1private val jsonParser = Json {
    2 ignoreUnknownKeys = true
    3 isLenient = true
    4 encodeDefaults = true
    5}
  2. Define a data class that represents the message to be parsed. For example, the data class representing the CarControl SetControllerValue message would look like this:

    1@Serializable
    2data class SetControllerValueIncomingMessage(
    3 val header: Header,
    4 val payload: SetControllerValueIncomingMessagePayload
    5)
    6
    7@Serializable
    8data class SetControllerValueIncomingMessagePayload(
    9 val controllerType: String,
    10 val endpointId: String,
    11 val turnOn: Boolean
    12)
  3. Decode the AASB message into an instance of the data class using the com.tomtom.ivi.platform.alexa.api.common.util.parseAasbMessage function.

    For example:

    1override suspend fun onMessageReceived(action: String, messageContents: String
    2): Boolean =
    3 when (action) {
    4 Action.CarControl.SET_CONTROLLER_VALUE -> handleSetControllerValue(messageContents)
    5 else -> false
    6 }
    7
    8private fun handleSetControllerValue(message: String): Boolean {
    9 val parsedMessage = parseAasbMessage<SetControllerValueIncomingMessage>(
    10 jsonParser,
    11 message
    12 )
    13 return true
    14}

Sending AASB "Reply" messages

In most cases, you will need to send a reply message to AACS to inform Alexa about whether the request was successfully executed or not. Typically, in order to send an AASB reply message, you would:

  1. Define a data class that represents the message to be sent to AACS. For example, the data class representing the CarControl SetControllerValueMessageReply message would look like this:

    1@Serializable
    2data class SetControllerValueOutgoingMessage(
    3 val header: Header,
    4 val payload: SetControllerValueOutgoingMessagePayload
    5)
    6
    7@Serializable
    8data class SetControllerValueOutgoingMessagePayload(
    9 val success: Boolean
    10)
  2. Create an instance of this class, encode it as a JSON string and send it using the AacsSenderWrapper helper class. You would normally also use the com.tomtom.ivi.platform.alexa.api.common.util.createAasbReplyHeader helper function to create the AASB reply header.

    For example:

    1private fun sendSetControllerValueReply(messageId: String, success: Boolean) {
    2 val messageToSend = SetControllerValueOutgoingMessage(
    3 createAasbReplyHeader(
    4 messageId,
    5 Topic.CAR_CONTROL,
    6 Action.CarControl.SET_CONTROLLER_VALUE
    7 ),
    8 SetControllerValueOutgoingMessagePayload(success)
    9 )
    10 val aacsSender = AacsSenderWrapper(iviServiceHostContext)
    11 aacsSender.sendMessage(
    12 jsonParser.encodeToString(messageToSend),
    13 Topic.CAR_CONTROL,
    14 Action.CarControl.SET_CONTROLLER_VALUE
    15 )
    16}

Sending AASB "Publish" messages

Some domains require the handler to proactively send a "Publish" message to AACS, for example, to send a request to Alexa or report an event. You can create these kinds of messages using the com.tomtom.ivi.platform.alexa.api.common.util.createAasbRequestNoPayload or com.tomtom.ivi.platform.alexa.api.common.util.createAasbRequestHeader helper functions. Typically, in order to send an AASB "Publish" message, you would:

  1. Define a data class that represents the message to be sent to AACS. For example, the data class representing the DoNotDisturbChanged message would look like this:

    1@Serializable
    2data class DoNotDisturbChangedOutgoingMessage(
    3 val header: Header,
    4 val payload: DoNotDisturbChangedOutgoingMessagePayload
    5)
    6
    7@Serializable
    8data class DoNotDisturbChangedOutgoingMessagePayload(
    9 val doNotDisturb: Boolean
    10)
  2. Create an instance of this class, encode it as a JSON string and send it using the AacsSenderWrapper helper class. You would normally also use the com.tomtom.ivi.platform.alexa.api.common.util.createAasbRequestHeader helper function to create the AASB Publish header.

    For example:

    1private fun sendDoNotDisturbChanged(messageId: String, success: Boolean) {
    2 val messageToSend = DoNotDisturbChangedOutgoingMessage(
    3 createAasbRequestHeader(
    4 messageId,
    5 Topic.DO_NOT_DISTURB,
    6 Action.DoNotDisturb.DO_NOT_DISTURB_CHANGED
    7 ),
    8 DoNotDisturbChangedOutgoingMessagePayload(value)
    9 )
    10 val aacsSender = AacsSenderWrapper(iviServiceHostContext)
    11 aacsSender.sendMessage(
    12 jsonParser.encodeToString(messageToSend),
    13 Topic.DO_NOT_DISTURB,
    14 Action.DoNotDisturb.DO_NOT_DISTURB_CHANGED
    15 )
    16}

Create a service host

Your module will also need to define a service host where the service will be running, as well as provide a service host builder. This can be achieved by creating 2 classes.

  1. A CustomAlexaHandlerServiceHost class:

    src/main/kotlin/com/example/ivi/example/alexa/customalexahandler/CustomAlexaHandlerServiceHost.kt

    1import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviDiscoverableServiceIdProvider
    2import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviServiceHostBase
    3import com.tomtom.ivi.platform.framework.api.ipc.iviservice.IviServiceHostContext
    4
    5/**
    6 * A [CustomAlexaHandlerService] host server.
    7 */
    8internal class CustomAlexaHandlerServiceHost(
    9 iviServiceHostContext: IviServiceHostContext,
    10 iviDiscoverableServiceIdProvider: IviDiscoverableServiceIdProvider
    11) :
    12 IviServiceHostBase(iviServiceHostContext) {
    13
    14 override val iviServices = setOf(
    15 CustomAlexaHandlerService(iviServiceHostContext, iviDiscoverableServiceIdProvider)
    16 )
    17}
  2. A CustomAlexaHandlerServiceHostBuilder class:

    src/main/kotlin/com/example/ivi/example/alexa/customalexahandler/CustomAlexaHandlerServiceHostBuilder.kt

    1/**
    2 * A [CustomAlexaHandlerServiceHost] builder used to build a [CustomAlexaHandlerService]
    3 * host.
    4 */
    5class CustomAlexaHandlerServiceHostBuilder : IviServiceHostBuilder() {
    6
    7 override fun build(iviServiceHostContext: IviServiceHostContext): IviServiceHostBase =
    8 CustomAlexaHandlerServiceHost(iviServiceHostContext) {
    9 getDiscoverableServiceId(it)
    10 }
    11
    12 companion object
    13}

Please ensure that the CustomAlexaHandlerServiceHostBuilder class is added to the root of your module hierarchy.

Configure the service host deployment

Define an IVI service host implementation in your gradle file, This can also be defined in a top-level gradle file (for example, iviservicehosts.gradle.kts) so it can be used in a multi-project build, including the tests.

examples/alexa/iviservicehosts.gradle.kts

1import com.tomtom.ivi.buildsrc.dependencies.ExampleModuleReference
2import com.tomtom.ivi.platform.gradle.api.common.dependencies.IviPlatformModuleReference
3import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceHostConfig
4import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceInterfaceConfig
5
6/**
7 * Defines a configuration for the custom Alexa Handler service.
8 *
9 * The configuration specifies the service host implementation and the list of interfaces
10 * implemented by this service host.
11 */
12val customAlexaHandlerServiceHost by extra {
13 IviServiceHostConfig(
14 serviceHostBuilderName = "CustomAlexaHandlerServiceHostBuilder",
15 implementationModule = ExampleModuleReference("services_customalexahandler"),
16 interfaces = listOf(
17 IviServiceInterfaceConfig(
18 serviceName = "AlexaHandlerService",
19 serviceId = "com.tomtom.ivi.example.service.customalexahandler",
20 serviceApiModule = IviPlatformModuleReference(
21 "platform_alexa_api_service_alexahandler"
22 )
23 )
24 )
25 )
26}

Register the service host build configuration in the main application's build script.

examples/alexa/app/build.gradle.kts

1import com.tomtom.ivi.platform.gradle.api.common.iviapplication.config.IviServiceHostConfig
2import com.tomtom.ivi.platform.gradle.api.framework.config.ivi
3
4// Define the service host configs as defined in the top-level `iviservicehosts.gradle.kts` file.
5apply(from = rootProject.file("examples/alexa/iviservicehosts.gradle.kts"))
6
7// Use Gradle's extra extensions to obtain the `customAlexaHandlerServiceHost` config as defined in
8// the top-level `iviservicehosts.gradle.kts` file.
9val customAlexaHandlerServiceHost: IviServiceHostConfig by project.extra
10
11ivi {
12 application {
13 enabled = true
14 services {
15 // Add the custom Alexa Handler service host to the application.
16 addHost(customAlexaHandlerServiceHost)
17 }
18 }
19}
20
21// The rest of the build script, dependencies, etc.