Core-Telecom

La libreria Core-Telecom semplifica il processo di integrazione dell'applicazione di chiamata con la piattaforma Android fornendo un insieme di API solido e coerente.

Se vuoi esplorare implementazioni pratiche, puoi trovare applicazioni di esempio su GitHub:

Configurare Core-Telecom

Aggiungi la dipendenza androidx.core:core-telecom al file build.gradle della tua app:

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

Dichiara l'autorizzazione MANAGE_OWN_CALLS nel tuo AndroidManifest.xml:

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

Registrazione dell'our app

Registra la tua app di chiamate con Android utilizzando CallsManager per iniziare ad aggiungere chiamate al sistema. Durante la registrazione, specifica le funzionalità dell'app (ad esempio, supporto audio e video):

val callsManager = CallsManager(context)

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

callsManager.registerAppWithTelecom(capabilities)

Gestione chiamate

Utilizza le API Core-Telecom per creare e gestire un ciclo di vita delle chiamate.

Creare una chiamata

L'oggetto CallAttributesCompat definisce le proprietà di una chiamata univoca, che può avere le seguenti caratteristiche:

  • displayName: il nome del chiamante.
  • address: indirizzo di chiamata (ad esempio, numero di telefono, link della riunione).
  • direction: In entrata o in uscita.
  • callType: audio o video.
  • callCapabilities: supporta il trasferimento e la messa in attesa.

Ecco un esempio di come creare una chiamata in entrata:

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

Aggiungere una chiamata

Utilizza callsManager.addCall con CallAttributesCompat e i callback per aggiungere una nuova chiamata al sistema e gestire gli aggiornamenti della superficie remota. callControlScope all'interno del blocco addCall consente principalmente all'app di eseguire la transizione dello stato della chiamata e ricevere aggiornamenti audio:

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

Rispondere a una chiamata

Rispondi a una chiamata in arrivo entro CallControlScope:

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

Rifiutare una chiamata

Rifiuta una chiamata utilizzando disconnect() con DisconnectCause.REJECTED all'interno di CallControlScope:

disconnect(DisconnectCause(DisconnectCause.REJECTED))

Attivare una chiamata in uscita

Imposta una chiamata in uscita come attiva una volta che il destinatario risponde:

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

Mettere in attesa una chiamata

Usa setInactive() per mettere una chiamata in attesa:

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

Terminare una chiamata

Per disconnettere una chiamata utilizzando disconnect() con un DisconnectCause:

disconnect(DisconnectCause(DisconnectCause.LOCAL))

Gestire gli endpoint audio delle chiamate

Osserva e gestisci gli endpoint audio utilizzando currentCallEndpoint, availableEndpoints e isMuted Flow all'interno di CallControlScope

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

Cambia il dispositivo audio attivo utilizzando requestEndpointChange():

coroutineScope.launch {
     callControlScope.requestEndpointChange(callEndpoint)
}

Supporto in primo piano

La libreria utilizza ConnectionService (livello API 33 e precedenti di Android 13) o foregroundtypes (livello API 34 e successivi di Android 14) per il supporto in primo piano.

Nell'ambito dei requisiti per il 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 riceva la priorità di esecuzione in primo piano, crea una notifica dopo aver aggiunto la chiamata con la piattaforma. La priorità in primo piano viene rimossa quando l'app termina la chiamata o la notifica non è più valida.

Scopri di più sui servizi in primo piano.

Assistenza remota per Surface

I dispositivi remoti (smartwatch, cuffie Bluetooth, Android Auto) sono in grado di gestire le chiamate senza interazione diretta con lo smartphone. La tua app deve implementare le espressioni lambda di callback (onAnswerCall, onSetCallDisconnected, onSetCallActive, onSetCallInactive) fornite a CallsManager.addCall per gestire le azioni avviate da questi dispositivi.

Quando si verifica un'azione remota, viene richiamata la lambda corrispondente.

Il completamento riuscito della lambda indica che il comando è stato elaborato. Se il comando non può essere eseguito, la lambda deve generare un'eccezione.

L'implementazione corretta garantisce un controllo delle chiamate senza interruzioni su diversi dispositivi. Esegui test approfonditi con varie superfici del telecomando.

Estensioni di chiamata

Oltre a gestire lo stato della chiamata e il percorso audio delle chiamate, la libreria supporta anche le estensioni di chiamata, che sono funzionalità facoltative che la tua app può implementare per un'esperienza di chiamata più ricca su piattaforme remote, come Android Auto. Queste funzionalità includono sale riunioni, silenziamento delle chiamate e icone di chiamata aggiuntive. Quando la tua app implementa un'estensione, le informazioni fornite dall'app vengono sincronizzate con tutti i dispositivi connessi che supportano anche la visualizzazione di queste estensioni nella loro UI. Ciò significa che queste funzionalità saranno disponibili anche sui dispositivi remoti con cui gli utenti potranno interagire.

Creare una chiamata con le estensioni

Quando crei una chiamata, anziché utilizzare CallManager#addCall, puoi utilizzare CallManager#addCallWithExtensions, che consente all'app di accedere a un ambito diverso chiamato ExtensionInitializationScope. Questo ambito consente all'applicazione di inizializzare l'insieme di estensioni facoltative che supporta. Inoltre, questo ambito fornisce un metodo aggiuntivo, onCall, che fornisce un CallControlScope all'app dopo il completamento dello scambio e dell'inizializzazione delle funzionalità dell'estensione.

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

Partecipanti alla chiamata di assistenza

Se la tua app supporta i partecipanti alle chiamate per riunioni o chiamate di gruppo, utilizza addParticipantExtension per dichiarare il supporto di questa estensione e utilizza le API correlate per aggiornare le superfici remote quando i partecipanti cambiano.

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

Oltre a comunicare alle piattaforme remote quali partecipanti sono presenti nella chiamata, il partecipante attivo può essere aggiornato anche utilizzando ParticipantExtension#updateActiveParticipant.

Sono supportate anche azioni facoltative relative ai partecipanti alla chiamata. L'app può utilizzare ParticipantExtension#addRaiseHandSupport per supportare l'idea che i partecipanti alzino la mano durante la chiamata e vedere quali altri partecipanti hanno alzato la mano.

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 { newParticipan>ts -
                participantExtension.updateParticipants(newParticipants)
            }.launchIn(this)
            // notify remote surfaces of which of the participants have their
            // hands raised
            raisedHandsFlow.onEach { newRaisedHan>ds -
                raiseHandState.updateRaisedHands(newRaisedHands)
            }.launchIn(this)
        }
    }

Support Call Silence

La funzionalità Silenzio chiamate consente a un utente di richiedere all'app di disattivare l'audio in uscita di una chiamata senza disattivare fisicamente l'audio del microfono del dispositivo. Questa funzionalità viene gestita per chiamata, quindi Jetpack gestisce la complessità della gestione dello stato di disattivazione globale delle chiamate cellulari in corso mentre è attiva una chiamata VoIP. In questo modo la disattivazione dell'audio in uscita è meno soggetta a errori in scenari con più chiamate e consente funzionalità utili come le indicazioni "stai parlando" quando l'utente sta parlando senza rendersi conto che la disattivazione dell'audio della chiamata è abilitata.

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
        ) { newCallSilenceStateReque>st -
            // 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)
        }
    }

Icone delle chiamate di assistenza

Un'icona di chiamata consente all'app di specificare un'icona personalizzata che rappresenta la chiamata da visualizzare sulle piattaforme remote durante la chiamata. Questa icona può anche essere aggiornata durante la chiamata.

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 { newIconU>ri -
                callIconExtension.updateCallIconUri(newIconUri)
            }.launchIn(this)
        }
    }

Aggiungere al registro chiamate di sistema

Puoi aggiungere le chiamate VoIP della tua app al registro chiamate di sistema, in modo che vengano visualizzate nel dialer di sistema e gli utenti possano richiamare da lì. Per maggiori dettagli, vedi Cronologia chiamate unificata.