Télécom

La nouvelle bibliothèque Android Telecom Jetpack permet d'indiquer facilement à la plate-forme l'état dans lequel se trouve votre appel. Vous trouverez le code source et un exemple d'application sur GitHub.

Dépendances et autorisations

Commencez par ouvrir le fichier build.gradle de votre module d'application et ajoutez une dépendance pour le module androidx Telecom:

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

Dans le fichier manifeste de votre application, déclarez que celle-ci utilise l'autorisation MANAGE_OWN_CALLS:

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

Enregistrer l'application

Pour informer Android de votre application, vous devez l'enregistrer et ses fonctionnalités. Cela indique à Android les fonctionnalités compatibles avec votre application, telles que les appels vidéo, le streaming d'appels et la mise en attente d'appels. Ces informations sont importantes pour qu'Android puisse se configurer de manière à utiliser les fonctionnalités de votre application.

 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)

Intégration de la plate-forme

Les deux scénarios d'appel les plus courants pour toute application appelante sont les appels entrants et sortants. Pour enregistrer correctement la direction de l'appel et avertir l'utilisateur de manière appropriée via des notifications, utilisez les API ci-dessous.

Enregistrer un appel

Cet exemple montre comment enregistrer un appel entrant:

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'objet callAttributes peut avoir les propriétés suivantes:

  • displayName: nom de l'appelant, de la réunion ou de la session.
  • address: adresse de l'appel. Notez qu'il peut s'agir d'un lien de réunion.
  • direction: direction de l'appel, par exemple entrant ou sortant.
  • callType: informations liées aux données transmises, telles que la vidéo et l'audio.
  • callCapabilities: objet spécifiant les capacités de l'appel.

L'objet callCapabilities peut avoir les propriétés suivantes:

  • streaming: indique si l'appel est compatible avec la diffusion audio en streaming sur un autre appareil Android.
  • transfer: indique si l'appel peut être transféré.
  • hold: indique si l'appel peut être mis en attente.

Ajouter un appel

La méthode addCall() renvoie une exception si l'appareil n'est pas compatible avec les télécommunications ou si une erreur s'est produite lors de la configuration de l'appel.

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

Répondre à un appel

Une fois que vous avez passé un appel entrant, vous devez répondre ou rejeter l'appel. Cet exemple montre comment répondre à un appel:

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

    }

    is CallControlResult.Error -> {

    }
}

Si un autre appel est en cours, answer() renvoie CallControlResult.Error, qui vous explique pourquoi l'appel n'a pas pu être pris. Dans ce cas, l'utilisateur doit mettre l'autre appel en attente.

Refuser un appel

Pour rejeter un appel, déconnectez-le avec DisconnectCause.Rejected.

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

Appel sortant

Lorsque vous passez un appel sortant, une fois que le tiers distant répond, vous devez définir l'appel sur active pour indiquer à la plate-forme qu'il est en cours:

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

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

Mettre un appel en attente

Si votre application d'appel prend en charge les appels en attente, utilisez setInActive pour indiquer à la plate-forme que votre appel n'est pas actif et que le micro et la caméra peuvent être utilisés par d'autres applications:

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

    }

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

Déconnecter

Pour déconnecter un appel, indiquez à la pile Telecom de se déconnecter en indiquant une cause valide:

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

Audio de l'itinéraire

Lors d'un appel, les utilisateurs passent parfois d'un appareil à l'autre, par exemple un haut-parleur, un écouteur ou un appareil Bluetooth. Utilisez les API availableEndpoints et currentCallEndpoint pour obtenir la liste de tous les appareils disponibles pour l'utilisateur et ceux qui sont actifs.

Cet exemple combine les deux flux pour créer un objet UI permettant à l'utilisateur de voir une liste d'appareils et lequel est actif:

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

Pour modifier un appareil actif, utilisez le requestEndpointChange avec le CallEndpoint que vous souhaitez utiliser.

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

Compatibilité avec le premier plan

La bibliothèque Telecom est compatible avec le premier plan. Cette bibliothèque utilise ConnectionService pour les appareils équipés d'Android 13 ou version antérieure. Sous Android 14 et versions ultérieures, elle utilise le micro et l'appareil photo des types de premier plan pour prendre en charge correctement les services de premier plan. En savoir plus sur les services de premier plan

Conformément aux exigences liées au premier plan, l'application doit publier une notification afin d'informer les utilisateurs que l'application s'exécute au premier plan.

Pour vous assurer que votre application obtiendra la priorité d'exécution au premier plan, créez une notification après avoir enregistré l'appel auprès de la plate-forme. La priorité au premier plan est supprimée lorsque votre application met fin à l'appel ou que votre notification n'est plus valide.

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

Surfaces compatibles

Les montres disposent d'une application générique de récepteur de point de terminaison. Cette application fournit à l'utilisateur une interface de base permettant, par exemple, de répondre, de rejeter et de déconnecter des appels. L'application prend en charge ces actions en implémentant des fonctions lambda qui indiquent à la plate-forme que vous avez effectué l'action sur l'appareil.

Chaque fonction lambda expire au bout de cinq secondes et génère une transaction ayant échoué si votre application ne répond pas.

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