Create a Custom Connection Service
The TomTom Digital Cockpit platform allows end-users to make phone calls using a Bluetooth-connected phone using the Bluetooth Hands Free Profile. TomTom Digital Cockpit can be extended to support other communications services such as VOIP (Voice Over Internet Protocol) calling or integrate an SDK of an online conference calling or messaging service and make and receive calls using the existing communications framework for these new services.
A ConnectionService
is a service used by Android to implement calling functionality, like phone calling or any VOIP
calling service like WhatsApp. Another communications service can be supported by implementing a
custom
ConnectionService
.
How to create a custom connection service
The following sections describe how to create a custom connection service implementation.
Example code illustrating how to implement the concepts described in this document can be found in
examples/telephony/customconnection
.
Service module setup
To create a custom connection service, add an AndroidManifest.xml
file to your module. This is
necessary to let it interact with the Android Telecom Manager service. In the added manifest it is
only necessary to configure the necessary permissions and intent filter, as the rest of the service
configuration will be done by the IVI runtime deployment system:
Add a
src/main/AndroidManifest.xml
file:
1<manifest xmlns:android="http://schemas.android.com/apk/res/android">23<!-- This is needed to place and receive calls using Android's TelecomManager. -->4<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />5<!-- This is needed to read available phone accounts using Android's TelecomManager. -->6<uses-permission android:name="android.permission.READ_PHONE_STATE" />78<application>9 <!-- Declaration of the custom connection service which can be bound with the TelecomManager. -->10 <service11 android:name=".CustomConnectionService"12 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">13 <intent-filter>14 <action android:name="android.telecom.ConnectionService" />15 </intent-filter>16 </service>17</application>18</manifest>
Add a dependency to the TelecomService
and
LifecycleService
in your
build.gradle.kts
file:
1dependencies {2 implementation(libraries.iviPlatformTelecomApiServiceTelecom)3 implementation("androidx.lifecycle:lifecycle-service:2.3.1")4}
The dependency to the TelecomService
is necessary in order to use
CallState
and
toPhoneUri
(package com.tomtom.ivi.platform.telecom.api.common.utils),
both required for the phone call state management.
The dependency to the
LifecycleService
is necessary in order to create a dispatcher in your custom service class, as it is not possible to
inherit from
LifecycleService
directly:
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionService.kt
private val dispatcher = ServiceLifecycleDispatcher(this)
Service preparation
To use your custom implementation you need to create an internal object
CustomConnectionServiceHolder.kt
that will create and store an instance of
CustomConnectionService
.
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionServiceHolder.kt
1internal object CustomConnectionServiceHolder {2 // The custom connection service used for communications.3 @Volatile4 private var customConnectionService: CustomConnectionService? = null56 // It should be called when we create the CustomConnectionService, so the instance of the7 // CustomConnectionService is initialized.8 @Synchronized9 fun setCustomConnectionService(customConnectionService: CustomConnectionService?) {10 this.customConnectionService = customConnectionService11 }1213 // Checks if the connection service is ready.14 @Synchronized15 fun isCustomConnectionServiceReady() = (customConnectionService != null)1617 //...18}
CustomConnectionService
will be set when the
TelecomManager
binds to
the CustomConnectionService
, which will happen when creating an incoming or outgoing call. If
there is no more ongoing call, the
TelecomManager
unbinds
the CustomConnectionService
and this shall be null.
Service definition
In order to create a connection service implementation you need to create a class that inherits from
the ConnectionService
and LifecycleOwner
classes. It should implement the required functions
from ConnectionService
like onCreateOutgoingConnection
, onCreateOutgoingConnectionFailed
, onCreateIncomingConnection
and onCreateIncomingConnectionFailed
. It should also implement getLifecycle
from
LifecycleService
.
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionService.kt
1internal class CustomConnectionService : ConnectionService(), LifecycleOwner {2 //...34 override fun onCreateOutgoingConnection(5 connectionManagerPhoneAccount: PhoneAccountHandle?,6 request: ConnectionRequest?7 ): Connection? {8 if (uriAlreadyHasAConnection(request?.address)) {9 tracer.uriAlreadyHasAConnection(request?.address)10 return null11 }12 return OutgoingCustomConnection(request).apply {13 setInitializing()14 setDialing()15 tracer.onCreateOutgoingConnection(this)16 }17 }1819 override fun onCreateOutgoingConnectionFailed(20 connectionManagerPhoneAccount: PhoneAccountHandle?,21 request: ConnectionRequest?22 ) {23 super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)24 tracer.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)25 }2627 override fun onCreateIncomingConnection(28 connectionManagerPhoneAccount: PhoneAccountHandle?,29 request: ConnectionRequest?3031 ): Connection? {32 if (uriAlreadyHasAConnection(request?.address)) {33 tracer.uriAlreadyHasAConnection(request?.address)34 return null35 }36 return IncomingCustomConnection(request).apply {37 setAddress(request?.address, TelecomManager.PRESENTATION_ALLOWED)38 setInitializing()39 setRinging()40 tracer.onCreateIncomingConnection(this)41 }42 }4344 override fun onCreateIncomingConnectionFailed(45 connectionManagerPhoneAccount: PhoneAccountHandle?,46 request: ConnectionRequest?47 ) {48 super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)49 tracer.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)50 }5152 //...5354 override fun getLifecycle() = dispatcher.lifecycle5556 //...57}
In this example we have created the CustomConnectionFacade
class that simulates incoming and
outgoing calls. This class is also able to change the current call's state. Simulated calls can be
created after registering a custom phone account in the system.
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionFacade.kt
1class CustomConnectionFacade(private val context: Context) {2 //...34 // Registers the custom phone account in the system. Needed before calling [createIncomingCall]5 // or [createOutgoingCall].6 private fun registerCustomPhoneAccount(): Boolean {7 //...8 telecomManager.registerPhoneAccount(customPhoneAccount)9 //...10 }1112 // Unregisters the custom phone account from the system. The recent calls related to the custom13 // phone account will be deleted as well.14 private fun unregisterCustomPhoneAccount(): Boolean {15 //...16 CustomConnectionServiceHolder.withCustomConnectionService {17 // Clean and destroy all pending connections if there are any.18 it.clearAllConnections()19 }20 telecomManager.unregisterPhoneAccount(customPhoneAccount.accountHandle)21 //...22 }2324 // Creates an incoming call using the [customPhoneAccount].25 private fun createIncomingCall(phoneNumber: String) {26 //...27 val extras = Bundle()28 val uri = phoneNumber.toPhoneUri()29 extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri)30 telecomManager.addNewIncomingCall(customPhoneAccount.accountHandle, extras)31 tracer.onCreatedIncomingCall(uri)32 }3334 // Creates an outgoing call using the [customPhoneAccount].35 private fun createOutgoingCall(phoneNumber: String) {36 //...37 val extras = Bundle()38 val uri = phoneNumber.toPhoneUri()39 extras.putParcelable(40 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,41 customPhoneAccount.accountHandle42 )43 //...44 telecomManager.placeCall(uri, extras)45 tracer.onCreatedOutgoingCall(phoneNumber.toPhoneUri())46 }4748 // Updates a call state.49 fun applyCallState(50 phoneNumber: String,51 callState: CallState,52 disconnectCause: DisconnectCause?53 ) {54 tracer.applyCallState(phoneNumber, callState, disconnectCause)55 CustomConnectionServiceHolder.withCustomConnectionService {56 // Change call's state.57 it.changeConnectionState(phoneNumber, callState, disconnectCause)58 }59 //...60 }6162 //...63}
Service lifecycle
To manage the initialization and destruction of the service, override the onCreate
and onDestroy
methods.
When the service is created:
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionService.kt
1override fun onCreate() {2 dispatcher.onServicePreSuperOnCreate()3 CustomConnectionServiceHolder.setCustomConnectionService(this)4 super.onCreate()5}
The CustomConnectionServiceHolder
creates and stores an instance of the
CustomConnectionService
.
When the service is destroyed:
src/main/kotlin/com/example/ivi/example/telephony/customconnection/CustomConnectionService.kt
1override fun onDestroy() {2 dispatcher.onServicePreSuperOnDestroy()3 super.onDestroy()4 CustomConnectionServiceHolder.setCustomConnectionService(null)5}
The instance of the CustomConnectionService
is null.
External links
- TomTom Digital Cockpit
TelecomService
. - Android
ConnectionService
.