The Core-Telecom
library streamlines the process of integrating your calling
application with the Android platform by providing a robust and consistent set
of APIs
If you want to explore practical implementations, you can find sample applications on GitHub:
- Lightweight Sample App — A minimal example
demonstrating
Core-Telecom
API usage. Ideal for quickly understanding fundamental concepts. - Comprehensive Sample App (Developed by the Core-Telecom Team) — A more feature-rich application showcasing advanced Telecom functionalities and best practices. This is a great resource for understanding complex integration scenarios.
Set up Core-Telecom
Add the androidx.core:core-telecom
dependency to your app's build.gradle
file:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0")
}
Declare the MANAGE_OWN_CALLS
permission in your AndroidManifest.xml
:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Register your app
Register your calling app with Android using CallsManager
to begin adding
calls to the system. When registering, specify your app's capabilities (for
example, audio, video support):
val callsManager = CallsManager(context)
val capabilities: @CallsManager.Companion.Capability Int =
(CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)
callsManager.registerAppWithTelecom(capabilities)
Call Management
Use Core-Telecom APIs to create and manage a call lifecycle.
Create a call
The CallAttributesCompat
object defines the properties of a unique call,
which can have the following characteristics:
displayName
: caller name.address
: Call address (for example, phone number, meeting link).direction
: Incoming or outgoing.callType
: Audio or video.callCapabilities
: Supports transfer and hold.
Here's an example of how to create an incoming call:
fun createIncomingCallAttributes(
callerName: String,
callerNumber: String,
isVideoCall: Boolean): CallAttributesCompat {
val addressUri = Uri.parse("YourAppScheme:$callerNumber")
// Define capabilities supported by your call.
val callCapabilities = CallAttributesCompat.CallCapability(
supportsSetInactive = CallAttributesCompat.SUPPORTS_SET_INACTIVE // Call can be made inactive (implies hold)
)
return CallAttributesCompat(
displayName = callerName,
address = addressUri,
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = if (isVideoCall) CallAttributesCompat.CALL_TYPE_VIDEO_CALL else CallAttributesCompat.CALL_TYPE_AUDIO_CALL,
callCapabilitiesCompat = callCapabilities
)
}
Add a call
Use callsManager.addCall
with CallAttributesCompat
and callbacks to add a
new call to the system and manage remote surface updates. The callControlScope
within the addCall
block primarily allows your app to transition the call
state and receive audio updates:
try {
callsManager.addCall(
INCOMING_CALL_ATTRIBUTES,
onAnswerCall, // Watch needs to know if it can answer the call.
onSetCallDisconnected,
onSetCallActive,
onSetCallInactive
) {
// The call was successfully added once this scope runs.
callControlScope = this
}
}
catch(addCallException: Exception){
// Handle the addCall failure.
}
Answer a call
Answer an incoming call within the CallControlScope
:
when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
is CallControlResult.Success -> { /* Call answered */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Reject a call
Reject a call using disconnect()
with DisconnectCause.REJECTED
within the
CallControlScope
:
disconnect(DisconnectCause(DisconnectCause.REJECTED))
Make an outgoing call active
Set an outgoing call to active once the remote party answers:
when (val result = setActive()) {
is CallControlResult.Success -> { /* Call active */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Place a call on hold
Use setInactive()
to put a call on hold:
when (val result = setInactive()) {
is CallControlResult.Success -> { /* Call on hold */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Disconnect a call
Disconnect a call using disconnect()
with a DisconnectCause
:
disconnect(DisconnectCause(DisconnectCause.LOCAL))
Manage call audio endpoints
Observe and manage audio endpoints using currentCallEndpoint
,
availableEndpoints
, and isMuted
Flow
s within the CallControlScope
fun observeAudioStateChanges(callControlScope: CallControlScope) {
with(callControlScope) {
launch { currentCallEndpoint.collect { /* Update UI */ } }
launch { availableEndpoints.collect { /* Update UI */ } }
launch { isMuted.collect { /* Handle mute state */ } }
}
}
Change the active audio device using requestEndpointChange()
:
coroutineScope.launch {
callControlScope.requestEndpointChange(callEndpoint)
}
Foreground support
The library uses ConnectionService
(Android 13 API level 33 and lower) or
foregroundtypes (Android 14 API level 34 and higher) for foreground
support.
As part of the foreground requirements, the application must post a notification for users to know that the application is running in the foreground.
To ensure that your app gets foreground execution priority, create a notification once you add the call with the platform. Foreground priority is removed when your app terminates the call or your notification is no longer valid.
Learn more about foreground services.
Remote Surface support
Remote devices (smartwatches, Bluetooth headsets, Android Auto) are capable of
call management without direct phone interaction. Your app must implement
callback lambdas (onAnswerCall
, onSetCallDisconnected
, onSetCallActive
,
onSetCallInactive
) provided to CallsManager.addCall
to handle actions
initiated by these devices.
When a remote action occurs, the corresponding lambda is invoked.
Successful completion of the lambda signals that the command was processed. If the command cannot be obeyed, the lambda should throw an exception.
Proper implementation ensures seamless call control across different devices. Test thoroughly with various remote surfaces.
Call Extensions
In addition to managing the call state and audio route of your calls, the library also supports call extensions, which are optional features that your app can implement for a richer calling experience on remote surfaces, such as Android Auto. These features include meeting rooms, call silence, and additional call icons. When your app implements an extension, information that the app provides will be synchronized with all of the connected devices that also support displaying these extensions in their UI. This means that these features will be also be available on remote devices for users to interact with.
Create a Call with Extensions
When creating a call, instead of using CallManager#addCall
to create the call,
you can instead use CallManager#addCallWithExtensions, which gives the
app access to a different scope called ExtensionInitializationScope
. This
scope allows the application to initialize the set of optional extensions that
it supports. Additionally, this scope provides an extra method, onCall
,
which provides a CallControlScope
back to the app after extension capability
exchange and initialization completes.
scope.launch {
mCallsManager.addCallWithExtensions(
attributes,
onAnswer,
onDisconnect,
onSetActive,
onSetInactive
) {
// Initialize extension-specific code...
// After the call has been initialized, perform in-call actions
onCall {
// Example: process call state updates
callStateFlow.onEach { newState ->
// handle call state updates and notify telecom
}.launchIn(this)
// Use initialized extensions...
}
}
}
Support Call Participants
If your app supports call participants for meetings or group calls, use
addParticipantExtension
to declare support for this extension and
use the related APIs to update remote surfaces when the participants change.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Notifies Jetpack that this app supports the participant
// extension and provides the initial participants state in the call.
val participantExtension = addParticipantExtension(
initialParticipants,
initialActiveParticipant
)
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// Example: update remote surfaces when the call participants change
participantsFlow.onEach { newParticipants ->
participantExtension.updateParticipants(newParticipants)
}.launchIn(this)
}
}
Along with notifying remote surfaces of what participants are in the call,
the active participant can also be updated using
ParticipantExtension#updateActiveParticipant
.
There's also support for optional actions related to the call participants.
The app can use ParticipantExtension#addRaiseHandSupport
to support the
notion of participants raising their hand in the call and see which other
participants also have their hands raised.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Notifies Jetpack that this app supports the participant
// extension and provides the initial list of participants in the call.
val participantExtension = addParticipantExtension(initialParticipants)
// Notifies Jetpack that this app supports the notion of participants
// being able to raise and lower their hands.
val raiseHandState = participantExtension.addRaiseHandSupport(
initialRaisedHands
) { onHandRaisedStateChanged ->
// handle this user's raised hand state changed updates from
// remote surfaces.
}
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// Example: update remote surfaces when the call participants change
participantsFlow.onEach { newParticipants ->
participantExtension.updateParticipants(newParticipants)
}.launchIn(this)
// notify remote surfaces of which of the participants have their
// hands raised
raisedHandsFlow.onEach { newRaisedHands ->
raiseHandState.updateRaisedHands(newRaisedHands)
}.launchIn(this)
}
}
Support Call Silence
Call silence allows a user to request that the app silence a call's outgoing audio without physically muting the device's microphone. This feature is managed per call, so Jetpack handles the complexity of managing the global mute state of ongoing cellular calls while a VOIP call is active. This makes silencing outgoing audio less error prone in multi-call scenarios while also allowing for helpful features such as "are you speaking" indications when the user is speaking while not realizing that call silence is enabled.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Add support for locally silencing the call's outgoing audio and
// register a handler for when the user changes the call silence state
// from a remote surface.
val callSilenceExtension = addLocalCallSilenceExtension(
initialCallSilenceState = false
) { newCallSilenceStateRequest ->
// handle the user's request to enable/disable call silence from
// a remote surface
}
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// When the call's call silence state changes, update remote
// surfaces of the new state.
callSilenceState.onEach { isSilenced ->
callSilenceExtension.updateIsLocallySilenced(isSilenced)
}.launchIn(this)
}
}
Support Call Icons
A call icon allows the app to specify a custom icon representing the call to be displayed on remote surfaces during the call. This icon can also be updated over the lifetime of the call.
mCallsManager.addCallWithExtensions(...) {
// Initialize extensions...
// Add support for a custom call icon to be displayed during the
// lifetime of the call.
val callIconExtension = addCallIconExtension(
initialCallIconUri = initialUri
)
// After the call has been initialized, perform in-call control actions
onCall {
// other in-call control and extension actions...
// When the call's icon changes, update remote surfaces by providing
// the new URI.
callIconUri.onEach { newIconUri ->
callIconExtension.updateCallIconUri(newIconUri)
}.launchIn(this)
}
}