Telecomunicações

A nova biblioteca Android Telecom Jetpack facilita dizer à plataforma o que em que a chamada está. Você pode encontrar o código-fonte e um app de exemplo em GitHub.

Dependências e permissões

Primeiro, abra o arquivo build.gradle do módulo do app e adicione uma dependência para o Módulo de telecomunicações do androidx:

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

No manifesto do app, declare que ele usa o MANAGE_OWN_CALLS. permissão:

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

Registrar o aplicativo

Para informar ao Android sobre seu app, é necessário registrá-lo e os recursos dele. Informa ao Android quais recursos seu app oferece suporte, como videochamadas, chamadas fazer streaming e reter ligações. Estas informações são importantes para que o Android possa configurar para funcionar 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 a plataforma

Os dois cenários de chamada mais comuns para qualquer aplicativo de chamada são e chamadas realizadas. Para registrar corretamente a direção da chamada e notificar adequadamente o usuário com notificações, usar as APIs abaixo.

Registrar uma ligação

Este exemplo demonstra como registrar uma ligação 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, reunião ou sessão.
  • address: o endereço da chamada. Observação: esse recurso pode ser estendido a uma reunião .
  • direction: a direção da chamada, como recebida ou realizada.
  • callType: informações relacionadas aos dados 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 ao streaming de áudio para outra 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 telecomunicações ou se ocorreu 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 chamada

Ao receber uma chamada, você precisa atender ou rejeitá-la. Isso exemplo demonstra como atender a uma ligação:

when (answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL)) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {

    }
}

Se outra chamada estiver em andamento, answer() vai retornar CallControlResult.Error, que informa por que não foi possível atender a chamada. Em Nesse caso, o usuário precisa colocar a outra chamada em espera.

Rejeitar uma chamada

Para rejeitar uma chamada, desconecte-a do DisconnectCause.Rejected.

fun onRejectCall(){
    coroutineScope.launch {
        callControlScope?.let {
            it.disconnect(DisconnectCause(DisconnectCause.REJECTED))
        }
    }
}

Chamada realizada

Ao realizar uma chamada, depois que o participante remoto atender, você deverá definir o chame active para informar à plataforma que a chamada está em andamento:

when (setActive()) {
    is CallControlResult.Success -> {
        onIsCallActive()
    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Colocar uma chamada em espera

Se seu app de chamadas oferecer suporte à retenção de chamadas, use setInActive para dizer ao plataforma em que sua chamada não está ativa e que o microfone e a câmera são livres para ser usados por outros apps:

when (setInActive()) {
    is CallControlResult.Success -> {

    }

    is CallControlResult.Error -> {
        updateCurrentCall {
            copy(errorCode = result.errorCode)
        }
    }
}

Desconectar

Para desconectar uma chamada, informe a pilha de telecomunicações para desconectar, fornecendo um causa válida:

coroutineScope.launch {
    callControlScope?.disconnect(DisconnectCause(DisconnectCause.LOCAL))
}

Rotear áudio

Durante uma chamada, os usuários às vezes alternam entre dispositivos, como alto-falantes, minifone de ouvido ou dispositivo Bluetooth. Use os métodos availableEndpoints e currentCallEndpoint APIs para receber 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 IU 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 é compatível com primeiro plano. Esta biblioteca usa ConnectionService para dispositivos com o Android 13 e versões anteriores. Para o Android 14 e maior, ele usará o microfone e a câmera primeiros tipos para que oferecer suporte a 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 que os usuários saibam que o aplicativo está sendo executado em primeiro plano.

Para garantir que o app receba a prioridade de execução em primeiro plano, crie uma assim que registrar a chamada com a plataforma. Prioridade em primeiro plano é removido quando o app encerra a chamada ou quando a notificação não é mais válidos.

is TelecomCall.Registered -> {
    val notification = createNotification(call)
    notificationManager.notify(TELECOM_NOTIFICATION_ID, notification)
}

Suporte da plataforma

Os relógios têm um aplicativo receptor de endpoint genérico. Esse aplicativo oferece o usuário com 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ê realizou a ação no dispositivo.

Cada função lambda expira após cinco segundos com uma transação com falha 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 = {}