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:
- App leve de exemplo: um exemplo mínimo
que demonstra o uso da API
Core-Telecom
. Ideal para entender rapidamente os conceitos fundamentais. - App de exemplo abrangente (desenvolvido pela equipe de Core-Telecom): um aplicativo com mais recursos que mostra as práticas recomendadas e funcionalidades avançadas de telecomunicações. Esse é um ótimo recurso para entender cenários de integração complexos.
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
Flow
s 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)
}
}