A nova biblioteca do Jetpack para telecomunicações do Android facilita informar à plataforma o estado da chamada. O código-fonte e um app de exemplo estão disponíveis no GitHub (em inglês).
Dependências e permissões
Primeiro, abra o arquivo build.gradle do módulo do app e adicione uma dependência ao módulo androidx Telecom:
dependencies {
implementation ("androidx.core:core-telecom:1.0.0-alpha02")
}
No manifesto do app, declare que ele usa a permissão
MANAGE_OWN_CALLS
:
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
Registrar o aplicativo
Para informar o Android sobre seu app, você precisa registrá-lo e os recursos dele. Isso informa ao Android quais recursos são compatíveis com o app, como videochamadas, streaming de chamadas e chamadas em espera. Essas informações são importantes para que o Android possa se configurar para trabalhar com os recursos do seu app.
private val callsManager = CallsManager(context)
var capabilities: @CallsManager.Companion.Capability Int =
CallsManager.CAPABILITY_BASELINE or
CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING
callsManager.registerAppWithTelecom(capabilities)
Integração com plataformas
Os dois cenários de chamada mais comuns para qualquer aplicativo de chamada são chamadas recebidas e efetuadas. Para registrar corretamente a direção da chamada e notificar o usuário adequadamente com notificações, use as APIs abaixo.
Registrar uma chamada
Este exemplo demonstra como registrar uma chamada recebida:
companion object {
const val APP_SCHEME = "MyCustomScheme"
const val ALL_CALL_CAPABILITIES = (CallAttributes.SUPPORTS_SET_INACTIVE
or CallAttributes.SUPPORTS_STREAM or CallAttributes.SUPPORTS_TRANSFER)
const val INCOMING_NAME = "Luke"
val INCOMING_URI: Uri = Uri.fromParts(APP_SCHEME, "", "")
// Define all possible properties for CallAttributes
val INCOMING_CALL_ATTRIBUTES =
CallAttributes(
INCOMING_NAME,
INCOMING_URI,
DIRECTION_INCOMING,
CALL_TYPE_VIDEO_CALL,
ALL_CALL_CAPABILITIES)
}
O objeto callAttributes
pode ter as seguintes propriedades:
displayName
: o nome do autor da chamada, da reunião ou da sessão.address
: o endereço da chamada. Observe que isso pode ser ampliado para um link de reunião.direction
: a direção da chamada, como recebida ou realizada.callType
: informações relacionadas aos dados que estão sendo transmitidos, como vídeo e áudio.callCapabilities
: um objeto que especifica os recursos da chamada.
O objeto callCapabilities
pode ter as seguintes propriedades:
streaming
: indica se a chamada oferece suporte a streaming de áudio para outro dispositivo Android.transfer
: indica se a chamada pode ser transferida.hold
: indica se a chamada pode ser colocada em espera.
Adicionar uma ligação
O método addCall()
retornará uma exceção se o dispositivo não oferecer suporte a
telecomunicações ou se ocorrer um erro ao configurar a chamada.
try {
callsManager.addCall(
INCOMING_CALL_ATTRIBUTES,
onIsCallAnswered, // Watch needs to know if it can answer the call
onIsCallDisconnected,
onIsCallActive,
onIsCallInactive
) {
callControlScope = this
}
}
Atender uma ligação
Depois de receber uma chamada, você deve atendê-la ou rejeitá-la. Este exame demonstra como atender uma ligação:
when (answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
is CallControlResult.Success -> {
}
is CallControlResult.Error -> {
}
}
Se outra chamada estiver em andamento, answer()
retornará
CallControlResult.Error
, que informa por que a chamada não pôde ser atendida. Nesse
caso, o usuário precisa colocar a outra chamada em espera.
Rejeitar uma chamada
Para rejeitar uma chamada, desconecte-a com DisconnectCause.Rejected
.
fun onRejectCall(){
coroutineScope.launch {
callControlScope?.let {
it.disconnect(DisconnectCause(DisconnectCause.REJECTED))
}
}
}
Chamada realizada
Ao fazer uma chamada, quando a outra parte atende, você precisa definir a chamada como active para que a plataforma saiba que ela está em andamento:
when (setActive()) {
is CallControlResult.Success -> {
onIsCallActive()
}
is CallControlResult.Error -> {
updateCurrentCall {
copy(errorCode = result.errorCode)
}
}
}
Colocar uma chamada em espera
Se o app de chamadas for compatível com chamadas, use setInActive
para informar à
plataforma que a chamada não está ativa e que o microfone e a câmera podem ser
usados por outros apps:
when (setInActive()) {
is CallControlResult.Success -> {
}
is CallControlResult.Error -> {
updateCurrentCall {
copy(errorCode = result.errorCode)
}
}
}
Desconectar
Para desconectar uma chamada, informe uma causa válida para que a pilha de telecomunicações se desconecte:
coroutineScope.launch {
callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL))
}
Encaminhar áudio
Durante uma chamada, às vezes os usuários alternam entre dispositivos, como alto-falante,
fone de ouvido ou dispositivo Bluetooth. Use as APIs availableEndpoints
e
currentCallEndpoint
para conferir uma lista de todos os dispositivos disponíveis para o
usuário e qual dispositivo está ativo.
Este exemplo combina os dois fluxos para criar um objeto de interface que mostra ao usuário uma lista de dispositivos e qual deles está ativo:
availableEndpoint = combine(callControlScope.availableEndpoints,
callControlScope.currentCallEndpoint) {
availableDevices: List<CallEndpoint>, activeDevice : CallEndpoint ->
availableDevices.map {
EndPointUI(
isActive = activeDevice.endpointName == it.endpointName, it
)
}
}
Para mudar um dispositivo ativo, use o requestEndpointChange
com o
CallEndpoint
que você quer usar.
coroutineScope.launch {
callControlScope?.requestEndpointChange(callEndpoint)
}
Suporte em primeiro plano
A biblioteca Telecom oferece suporte em primeiro plano. Essa biblioteca usa
ConnectionService
para dispositivos com o Android 13 e versões anteriores. No Android 14 e
versões mais recentes, ele usa o microfone e a câmera dos tipos em primeiro plano para oferecer suporte
correta aos serviços em primeiro plano. Saiba mais sobre os serviços em primeiro plano.
Como parte dos requisitos de primeiro plano, o aplicativo precisa postar uma notificação para os usuários saberem que o aplicativo está sendo executado em primeiro plano.
Para garantir que o app tenha prioridade de execução em primeiro plano, crie uma notificação depois de registrar a chamada com a plataforma. A prioridade em primeiro plano é removida quando o app encerra a chamada ou a notificação não é mais válida.
is TelecomCall.Registered -> {
val notification = createNotification(call)
notificationManager.notify(TELECOM_NOTIFICATION_ID, notification)
}
Suporte a superfícies
Os smartwatches têm um aplicativo receptor de endpoint genérico. Esse aplicativo fornece ao usuário uma interface básica, como atender, rejeitar e desconectar chamadas. O aplicativo oferece suporte a essas ações implementando funções lambda que informam à plataforma que você executou a ação no dispositivo.
Cada função lambda expira após cinco segundos com uma transação com falha se o aplicativo não responde.
callsManager.addCall(
attributes,
onIsCallAnswered, // Watch/Auto need to know if they can answer the call
onIsCallDisconnected,
onIsCallActive,
onIsCallInactive
) {
//Call Scope
}
/**
* Can the call be successfully answered??
* TIP: Check the connection/call state to see if you can answer a call
* Example you may need to wait for another call to hold.
**/
val onIsCallAnswered: suspend(type: Int) -> Unit = {}
/**
* Can the call perform a disconnect
*/
val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {}
/**
* Check is see if you can make the call active.
* Other calls and state might stop us from activating the call
*/
val onIsCallActive: suspend () -> Unit = {
updateCurrentCall {
}
}
/**
* Check to see if you can make the call inactivate
*/
val onIsCallInactive: suspend () -> Unit = {}