Core-Telecom

A biblioteca Core-Telecom simplifica o processo de integração do seu aplicativo de chamada com a plataforma Android, fornecendo um conjunto robusto e consistente de APIs.

Se você quiser conferir implementações práticas, confira os exemplos de aplicativos no GitHub:

Configurar o Core-Telecom

Adicione a dependência androidx.core:core-telecom ao arquivo build.gradle do app:

dependencies {
    implementation ("androidx.core:core-telecom:1.0.0")
}

Declare a permissão MANAGE_OWN_CALLS no AndroidManifest.xml:

<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

Registrar seu aplicativo

Registre seu app de chamada no Android usando CallsManager para começar a adicionar chamadas ao sistema. Ao fazer o registro, especifique os recursos do app (por exemplo, suporte a áudio e vídeo):

val callsManager = CallsManager(context)

val capabilities: @CallsManager.Companion.Capability Int =
    (CallsManager.CAPABILITY_BASELINE or
          CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING)

callsManager.registerAppWithTelecom(capabilities)

Gerenciamento de chamadas

Use as APIs Core-Telecom para criar e gerenciar o ciclo de vida de uma chamada.

Criar uma ligação

O objeto CallAttributesCompat define as propriedades de uma chamada única, que pode ter as seguintes características:

  • displayName: nome do autor da chamada.
  • address: endereço de chamada (por exemplo, número de telefone, link da reunião).
  • direction: entrada ou saída.
  • callType: áudio ou vídeo.
  • callCapabilities: oferece suporte a transferência e retenção.

Confira um exemplo de como criar uma chamada recebida:

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
    )
}

Adicionar uma ligação

Use callsManager.addCall com CallAttributesCompat e callbacks para adicionar uma nova chamada ao sistema e gerenciar atualizações de superfície remotas. O callControlScope no bloco addCall permite principalmente que o app faça a transição do estado da chamada e receba atualizações de áudio:

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.
}

Atender uma chamada

Atender uma ligação recebida no CallControlScope:

when (val result = answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> { /* Call answered */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Rejeitar uma ligação

Rejeite uma chamada usando disconnect() com DisconnectCause.REJECTED no CallControlScope:

disconnect(DisconnectCause(DisconnectCause.REJECTED))

Ativar uma chamada de saída

Defina uma chamada de saída como ativa quando a parte remota atender:

when (val result = setActive()) {
    is CallControlResult.Success -> { /* Call active */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Colocar uma chamada em espera

Use setInactive() para colocar uma chamada em espera:

when (val result = setInactive()) {
    is CallControlResult.Success -> { /* Call on hold */ }
    is CallControlResult.Error -> { /* Handle error */ }
}

Desconectar uma chamada

Desconecte uma chamada usando disconnect() com um DisconnectCause:

disconnect(DisconnectCause(DisconnectCause.LOCAL))

Gerenciar endpoints de áudio de chamada

Observar e gerenciar endpoints de áudio usando currentCallEndpoint, availableEndpoints e isMuted Flows no CallControlScope

fun observeAudioStateChanges(callControlScope: CallControlScope) {
    with(callControlScope) {
        launch { currentCallEndpoint.collect { /* Update UI */ } }
        launch { availableEndpoints.collect { /* Update UI */ } }
        launch { isMuted.collect { /* Handle mute state */ } }
    }
}

Mude o dispositivo de áudio ativo usando requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

Suporte em primeiro plano

A biblioteca usa ConnectionService (Android 13 API de nível 33 e versões anteriores) ou foregroundtypes (Android 14 API de nível 34 e mais recentes) para oferecer suporte ao primeiro plano.

Como parte dos requisitos em primeiro plano, o aplicativo precisa postar uma notificação para que os usuários saibam que ele está em execução em primeiro plano.

Para garantir que o app tenha prioridade de execução em primeiro plano, crie uma notificação depois de adicionar a chamada com a plataforma. A prioridade em primeiro plano é removida quando o app encerra a chamada ou a notificação deixa de ser válida.

Saiba mais sobre os serviços em primeiro plano.

Suporte remoto ao Surface

Dispositivos remotos (smartwatches, fones de ouvido Bluetooth, Android Auto) podem gerenciar chamadas sem interação direta com o smartphone. O app precisa implementar lambdas de callback (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) fornecidos para CallsManager.addCall para processar ações iniciadas por esses dispositivos.

Quando uma ação remota ocorre, a lambda correspondente é invocada.

A conclusão bem-sucedida da lambda indica que o comando foi processado. Se o comando não puder ser obedecido, o lambda vai gerar uma exceção.

A implementação adequada garante o controle de chamadas perfeito em diferentes dispositivos. Teste completamente com várias superfícies remotas.

Extensões de chamada

Além de gerenciar o estado e a rota de áudio das chamadas, a biblioteca também oferece suporte a extensões de chamada, que são recursos opcionais que o app pode implementar para uma experiência de chamada mais rica em plataformas remotas, como o Android Auto. Esses recursos incluem salas de reunião, silenciar chamadas e outros ícones de chamada. Quando o app implementa uma extensão, as informações que ele oferece são sincronizadas com todos os dispositivos conectados que também têm suporte à exibição dessas extensões na interface. Isso significa que esses recursos também estarão disponíveis em dispositivos remotos para interação dos usuários.

Criar uma chamada com extensões

Ao criar uma chamada, em vez de usar CallManager#addCall para criar a chamada, use CallManager#addCallWithExtensions, que dá ao app acesso a um escopo diferente chamado ExtensionInitializationScope. Esse escopo permite que o aplicativo inicialize o conjunto de extensões opcionais que são compatíveis. Além disso, esse escopo fornece um método extra, onCall, que fornece um CallControlScope de volta ao app após a troca e a inicialização do recurso de extensão serem concluídas.

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...
        }
    }
}

Suporte para participantes de chamadas

Se o app oferece suporte a participantes de chamadas para reuniões ou chamadas em grupo, use addParticipantExtension para declarar suporte a essa extensão e use as APIs relacionadas para atualizar as plataformas remotas quando os participantes mudarem.

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)
        }
    }

Além de notificar as plataformas remotas sobre os participantes na chamada, o participante ativo também pode ser atualizado usando ParticipantExtension#updateActiveParticipant.

Também há suporte para ações opcionais relacionadas aos participantes da chamada. O app pode usar ParticipantExtension#addRaiseHandSupport para oferecer suporte à noção de participantes que levantam a mão na chamada e conferir quais outros participantes também têm as mãos levantadas.

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)
        }
    }

Suporte para silenciar chamadas

O silenciamento de chamadas permite que o usuário solicite que o app silencie o áudio de saída de uma chamada sem silenciar fisicamente o microfone do dispositivo. Esse recurso é gerenciado por chamada. Portanto, o Jetpack lida com a complexidade de gerenciar o estado de silenciamento global de chamadas celulares em andamento enquanto uma chamada VOIP está ativa. Isso torna o silenciamento de áudio de saída menos propenso a erros em cenários de várias chamadas, além de permitir recursos úteis, como indicações de "você está falando" quando o usuário está falando sem perceber que o silêncio de chamada está ativado.

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)
        }
    }

Ícones de chamada de suporte

Um ícone de chamada permite que o app especifique um ícone personalizado que representa a chamada a ser exibida em plataformas remotas durante a chamada. Esse ícone também pode ser atualizado durante a duração da chamada.

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)
        }
    }