Telecomunicazioni

La nuova libreria Android Telecom Jetpack consente di indicare facilmente alla piattaforma lo stato della chiamata. Puoi trovare il codice sorgente e un'app di esempio su GitHub.

Dipendenze e autorizzazioni

Innanzitutto, apri il file build.gradle del modulo dell'app e aggiungi una dipendenza per il modulo androidx Telecom:

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

Nel file manifest dell'app, dichiara che la tua app utilizza l'autorizzazione MANAGE_OWN_CALLS:

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

Registra l'applicazione

Per comunicare ad Android la tua app, devi registrarla e le sue funzionalità. Questo indica ad Android le funzionalità supportate dalla tua app, come le videochiamate, lo streaming di chiamate e l'attesa. Queste informazioni sono importanti per consentire ad Android di configurarsi per funzionare con le funzionalità della tua 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)

Integrazione della piattaforma

I due scenari di chiamata più comuni per qualsiasi applicazione di chiamata sono le chiamate in entrata e in uscita. Per registrare correttamente la direzione della chiamata e informare in modo appropriato l'utente con le notifiche, utilizza le API seguenti.

Registra una chiamata

Questo esempio mostra come registrare una chiamata in arrivo:

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

L'oggetto callAttributes può avere le seguenti proprietà:

  • displayName: nome del chiamante, della riunione o della sessione.
  • address: l'indirizzo della chiamata. Nota: può essere esteso a un link alla riunione.
  • direction: la direzione della chiamata, ad esempio in arrivo o in uscita.
  • callType: informazioni relative ai dati trasmessi, come video e audio.
  • callCapabilities: un oggetto che specifica le funzionalità della chiamata.

L'oggetto callCapabilities può avere le seguenti proprietà:

  • streaming: indica se la chiamata supporta lo streaming dell'audio su un altro dispositivo Android.
  • transfer: indica se è possibile trasferire la chiamata.
  • hold: indica se la chiamata può essere messa in attesa.

Aggiungi una chiamata

Il metodo addCall() restituisce un'eccezione se il dispositivo non supporta le telecomunicazioni o se si è verificato un errore durante la configurazione della chiamata.

try {
    callsManager.addCall(
        INCOMING_CALL_ATTRIBUTES,
        onIsCallAnswered, // Watch needs to know if it can answer the call
        onIsCallDisconnected,
        onIsCallActive,
        onIsCallInactive
    ) {
        callControlScope = this
    }
}

Rispondere a una chiamata

Dopo aver effettuato una chiamata in arrivo, devi rispondere o rifiutare la chiamata. Questo esempio mostra come rispondere a una chiamata:

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

    }

    is CallControlResult.Error -> {

    }
}

Se è in corso un'altra chiamata, answer() restituirà CallControlResult.Error, che indica il motivo per cui non è stato possibile rispondere alla chiamata. In questo caso, l'utente deve mettere in attesa l'altra chiamata.

Rifiutare una chiamata

Per rifiutare una chiamata, disconnettila con DisconnectCause.Rejected.

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

Chiamata in uscita

Quando effettui una chiamata in uscita, dopo che l'utente remoto risponde, devi impostare la chiamata su attiva per comunicare alla piattaforma che la chiamata è in corso:

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

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

Mettere in attesa una chiamata

Se la tua app per le chiamate supporta le chiamate in attesa, usa setInActive per comunicare alla piattaforma che la chiamata non è attiva e che altre app possono utilizzare liberamente microfono e fotocamera:

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

    }

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

Disconnetterti

Per disconnettere una chiamata, chiedi allo stack di telecomunicazioni di disconnettersi fornendo una causa valida:

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

Percorso audio

Durante una chiamata, a volte gli utenti passano da un dispositivo all'altro, ad esempio un altoparlante, un auricolare o un dispositivo Bluetooth. Utilizza le API availableEndpoints e currentCallEndpoint per ottenere un elenco di tutti i dispositivi disponibili per l'utente e di quelli attivi.

Questo esempio combina entrambi i flussi per creare un oggetto UI per mostrare all'utente un elenco di dispositivi e quale è attivo:

availableEndpoint = combine(callControlScope.availableEndpoints,
    callControlScope.currentCallEndpoint) {
    availableDevices: List<CallEndpoint>, activeDevice : CallEndpoint ->
    availableDevices.map {
        EndPointUI(
            isActive = activeDevice.endpointName == it.endpointName, it
        )
    }
}

Per cambiare un dispositivo attivo, usa requestEndpointChange con il CallEndpoint a cui vuoi passare.

coroutineScope.launch {
     callControlScope?.requestEndpointChange(callEndpoint)
}

Supporto in primo piano

La libreria Telecom include il supporto in primo piano. Questa libreria utilizza ConnectionService per i dispositivi con Android 13 e versioni precedenti. Per Android 14 e versioni successive, vengono utilizzati il microfono e la fotocamera dei tipi in primo piano per supportare correttamente i servizi in primo piano. Scopri di più sui servizi in primo piano.

Come parte dei requisiti in primo piano, l'applicazione deve pubblicare una notifica per informare gli utenti che l'applicazione è in esecuzione in primo piano.

Per assicurarti che la tua app abbia la priorità di esecuzione in primo piano, crea una notifica dopo aver registrato la chiamata con la piattaforma. La priorità in primo piano viene rimossa quando l'app termina la chiamata o la notifica non è più valida.

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

Surface Support

Gli smartwatch hanno un'applicazione di ricezione dell'endpoint generica. Questa applicazione fornisce all'utente un'interfaccia di base come la risposta, il rifiuto e la disconnessione delle chiamate. L'applicazione supporta queste azioni implementando funzioni lambda che informano la piattaforma su cui hai eseguito l'azione sul dispositivo.

Se l'applicazione non risponde, ogni funzione lambda scade dopo 5 secondi con una transazione non riuscita.

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 = {}