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:
- App di esempio leggera: un esempio minimo che mostra l'utilizzo dell'API
Core-Telecom. Ideale per comprendere rapidamente i concetti fondamentali. - App di esempio completa (sviluppata dal team Core-Telecom): un'applicazione più ricca di funzionalità che mostra le funzionalità avanzate di Telecom e le best practice. Questa è un'ottima risorsa per comprendere scenari di integrazione complessi.
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 in 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 della 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")
return CallAttributesCompat(
displayName = callerName,
address = addressUri,
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = if (isVideoCall) {
CallAttributesCompat.CALL_TYPE_VIDEO_CALL
} else {
CallAttributesCompat.CALL_TYPE_AUDIO_CALL
},
callCapabilities = CallAttributesCompat.SUPPORTS_SET_INACTIVE
)
}
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. Il 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 la persona remota 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 */ }
}
Disconnettere 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. Non
utilizzare le API AudioManager#setCommunicationDevice o
AudioManager#startBluetoothSco per gestire i percorsi audio quando utilizzi Telecom;
in questo modo si verificheranno problemi audio durante la chiamata.
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 su Android 13 (livello API 33) e versioni precedenti oppure
tipi di servizi in primo piano su Android 14 (livello API 34) e versioni successive per
il supporto in primo piano.
Per mantenere attive le chiamate quando l'app è in background, ospita CallsManager
all'interno di un Service in primo piano (ad esempio LifecycleService) e dichiara il tipo di servizio in primo piano phoneCall nel tuo AndroidManifest.xml:
<service
android:name=".TelecomVoipService"
android:foregroundServiceType="phoneCall" />
Nell'ambito dei requisiti per il servizio in primo piano, la tua app deve pubblicare una notifica NotificationCompat.CallStyle in modo che gli utenti sappiano che una chiamata è attiva in primo piano. Per assicurarti che la tua app abbia la priorità di esecuzione in primo piano, promuovi il tuo servizio in primo piano utilizzando startForeground una volta
dopo aver aggiunto la chiamata con la piattaforma:
startForeground(
notificationId,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
)
Scopri di più sui servizi in primo piano.
Supporto remoto 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.
La corretta implementazione 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 CallsManager.addCall, puoi utilizzare CallsManager.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 notificare alle superfici remote i partecipanti alla 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 { 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)
}
}
Supporto per la funzionalità Silenzia chiamata
La funzionalità Silenzio chiamate consente a un utente di richiedere all'app di disattivare l'audio in uscita di una chiamata senza disattivare fisicamente il 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 una chiamata VoIP è attiva. In questo modo, la disattivazione dell'audio in uscita è meno soggetta a errori in scenari multichiamata e consente anche 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
) { 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)
}
}
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 { newIconUri ->
callIconExtension.updateCallIconUri(newIconUri)
}.launchIn(this)
}
}
Aggiungere al registro chiamate del 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.