Core-Telecom

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 de APIs sólido y coherente.

Si deseas explorar implementaciones prácticas, puedes encontrar aplicaciones de muestra en GitHub:

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. Realiza pruebas exhaustivas con varias plataformas remotas.