La biblioteca Core-Telecom
optimiza el proceso de integración de tu aplicación de llamadas con la plataforma de Android, ya que proporciona un conjunto sólido y coherente de APIs.
Si quieres explorar implementaciones prácticas, puedes encontrar aplicaciones de muestra en GitHub:
- App de ejemplo liviana: Es un ejemplo mínimo que demuestra el uso de la API de
Core-Telecom
. Ideal para comprender rápidamente los conceptos fundamentales. - Aplicación de ejemplo integral (desarrollada por el equipo de Core-Telecom): Es una aplicación con más funciones que muestra prácticas recomendadas y funciones avanzadas de telecomunicaciones. Este es un gran recurso para comprender situaciones de integración complejas.
Configura Core-Telecom
Agrega la dependencia androidx.core:core-telecom
al archivo build.gradle
de tu app:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0")
}
Declara el permiso MANAGE_OWN_CALLS
en tu AndroidManifest.xml
:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Cómo registrar la aplicación
Registra tu app de llamadas con Android usando CallsManager
para comenzar a agregar llamadas al sistema. Cuando te registres, especifica las capacidades de tu app (por ejemplo, compatibilidad con audio y video):
val callsManager = CallsManager(context)
val capabilities: @CallsManager.Companion.Capability Int =
(CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)
callsManager.registerAppWithTelecom(capabilities)
Administración de llamadas
Usa las APIs de Core-Telecom para crear y administrar un ciclo de vida de llamadas.
Cómo crear una llamada
El objeto CallAttributesCompat
define las propiedades de una llamada única,
que puede tener las siguientes características:
displayName
: Es el nombre del emisor.address
: Es la dirección de llamada (por ejemplo, número de teléfono o vínculo de reunión).direction
: Llamada entrante o saliente.callType
: Audio o video.callCapabilities
: Admite transferencia y retención.
Este es un ejemplo de cómo crear una llamada entrante:
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
)
}
Cómo agregar una llamada
Usa callsManager.addCall
con CallAttributesCompat
y devoluciones de llamada para agregar una llamada nueva al sistema y administrar las actualizaciones de la superficie remota. El callControlScope
dentro del bloque addCall
permite principalmente que tu app realice la transición del estado de la llamada y reciba actualizaciones de audio:
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.
}
Cómo responder una llamada
Responde una llamada entrante dentro de CallControlScope
:
when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
is CallControlResult.Success -> { /* Call answered */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Cómo rechazar una llamada
Rechaza una llamada con disconnect()
con DisconnectCause.REJECTED
dentro de CallControlScope
:
disconnect(DisconnectCause(DisconnectCause.REJECTED))
Cómo activar una llamada saliente
Cómo establecer una llamada saliente como activa una vez que la parte remota responde:
when (val result = setActive()) {
is CallControlResult.Success -> { /* Call active */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Coloca una llamada en espera
Usa setInactive()
para poner una llamada en espera:
when (val result = setInactive()) {
is CallControlResult.Success -> { /* Call on hold */ }
is CallControlResult.Error -> { /* Handle error */ }
}
Cómo desconectar una llamada
Desconecta una llamada con disconnect()
con un DisconnectCause
:
disconnect(DisconnectCause(DisconnectCause.LOCAL))
Administra los extremos de audio de las llamadas
Observa y administra los extremos de audio con Flow
de currentCallEndpoint
, availableEndpoints
y isMuted
dentro de CallControlScope
fun observeAudioStateChanges(callControlScope: CallControlScope) {
with(callControlScope) {
launch { currentCallEndpoint.collect { /* Update UI */ } }
launch { availableEndpoints.collect { /* Update UI */ } }
launch { isMuted.collect { /* Handle mute state */ } }
}
}
Cambia el dispositivo de audio activo con requestEndpointChange()
:
coroutineScope.launch {
callControlScope.requestEndpointChange(callEndpoint)
}
Compatibilidad con primer plano
La biblioteca usa ConnectionService
(Android 13 nivel de API 33 y versiones anteriores) o foregroundtypes (Android 14 nivel de API 34 y versiones posteriores) para la compatibilidad en primer plano.
Como parte de los requisitos en primer plano, la aplicación debe publicar una notificación para que los usuarios sepan que se está ejecutando en primer plano.
Para asegurarte de que tu app obtenga prioridad de ejecución en primer plano, crea una notificación una vez que agregues la llamada con la plataforma. La prioridad en primer plano se quita cuando tu app finaliza la llamada o cuando tu notificación ya no es válida.
Obtén más información sobre los servicios en primer plano.
Compatibilidad remota con Surface
Los dispositivos remotos (relojes inteligentes, auriculares Bluetooth y Android Auto) pueden administrar llamadas sin interactuar directamente con el teléfono. Tu app debe implementar las lambdas de devolución de llamada (onAnswerCall
, onSetCallDisconnected
, onSetCallActive
, onSetCallInactive
) que se proporcionan a CallsManager.addCall
para controlar las acciones que inician estos dispositivos.
Cuando se produce una acción remota, se invoca la lambda correspondiente.
Completación correcta de los indicadores lambda que indican que se procesó el comando. Si no se puede obedecer el comando, la expresión lambda debe arrojar una excepción.
La implementación adecuada garantiza un control de llamadas sin problemas en diferentes dispositivos. Prueba exhaustivamente con varias plataformas remotas.
Extensiones de llamada
Además de administrar el estado de las llamadas y la ruta de audio, la biblioteca también admite extensiones de llamadas, que son funciones opcionales que tu app puede implementar para brindar una experiencia de llamadas más enriquecida en plataformas remotas, como Android Auto. Estas funciones incluyen salas de reuniones, silenciamiento de llamadas y íconos de llamadas adicionales. Cuando tu app implemente una extensión, la información que proporciona se sincronizará con todos los dispositivos conectados que también admitan la visualización de estas extensiones en su IU. Esto significa que estas funciones también estarán disponibles en dispositivos remotos para que los usuarios interactúen con ellas.
Cómo crear una llamada con extensiones
Cuando crees una llamada, en lugar de usar CallManager#addCall
, puedes usar CallManager#addCallWithExtensions, que le otorga a la app acceso a un alcance diferente llamado ExtensionInitializationScope
. Este alcance permite que la aplicación inicialice el conjunto de extensiones opcionales que admite. Además, este alcance proporciona un método adicional, onCall
, que devuelve un CallControlScope
a la app después de que se completa el intercambio y la inicialización de la capability de extensión.
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...
}
}
}
Cómo brindar asistencia a los participantes de una llamada
Si tu app admite participantes de llamadas para reuniones o llamadas grupales, usa addParticipantExtension
para declarar la compatibilidad con esta extensión y usa las APIs relacionadas para actualizar las plataformas remotas cuando cambien los participantes.
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)
}
}
Además de notificar a las plataformas remotas qué participantes están en la llamada, el participante activo también se puede actualizar con ParticipantExtension#updateActiveParticipant
.
También se admite la ejecución de acciones opcionales relacionadas con los participantes de la llamada.
La app puede usar ParticipantExtension#addRaiseHandSupport
para admitir la idea de que los participantes levanten la mano en la llamada y ver qué otros participantes también tienen la mano levantada.
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)
}
}
Cómo silenciar llamadas de asistencia
La función de silenciar llamadas permite que un usuario solicite que la app silencie el audio saliente de una llamada sin silenciar físicamente el micrófono del dispositivo. Esta función se administra por llamada, por lo que Jetpack controla la complejidad de administrar el estado de silenciamiento global de las llamadas celulares en curso mientras una llamada de VoIP está activa. Esto hace que silenciar el audio saliente sea menos propenso a errores en situaciones de varias llamadas y, al mismo tiempo, permite funciones útiles, como las indicaciones de "¿estás hablando?" cuando el usuario está hablando sin darse cuenta de que la función de silenciar llamadas está habilitada.
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)
}
}
Íconos de llamada de asistencia
Un ícono de llamada permite que la app especifique un ícono personalizado que represente la llamada que se mostrará en las plataformas remotas durante la llamada. Este ícono también se puede actualizar durante la vida útil de la llamada.
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)
}
}