CallKit dans Xamarin.iOS
La nouvelle API CallKit dans iOS 10 permet aux applications VOIP de s’intégrer à l’interface utilisateur i Téléphone et de fournir une interface et une expérience familières à l’utilisateur final. Avec cette API, les utilisateurs peuvent afficher et interagir avec les appels VOIP à partir de l’écran de verrouillage de l’appareil iOS et gérer les contacts à l’aide des vues Favoris et Récents de l’application Téléphone.
À propos de CallKit
Selon Apple, CallKit est une nouvelle infrastructure qui élèvera les applications VoIP (Voice Over IP) tierces à une expérience tierce sur iOS 10. L’API CallKit permet aux applications VOIP de s’intégrer à l’interface utilisateur i Téléphone et de fournir une interface et une expérience familières à l’utilisateur final. Tout comme l’application intégrée Téléphone, un utilisateur peut afficher et interagir avec les appels VOIP à partir de l’écran de verrouillage de l’appareil iOS et gérer les contacts à l’aide des vues Favoris et Récents de l’application Téléphone.
En outre, l’API CallKit permet de créer des extensions d’application qui peuvent associer un numéro de téléphone à un nom (ID de l’appelant) ou indiquer au système quand un numéro doit être bloqué (blocage des appels).
Expérience d’application VOIP existante
Avant de discuter de la nouvelle API CallKit et de ses capacités, examinez l’expérience utilisateur actuelle avec une application VOIP tierce dans iOS 9 (et moins) à l’aide d’une application VOIP fictive appelée MonkeyCall. MonkeyCall est une application simple qui permet à l’utilisateur d’envoyer et de recevoir des appels VOIP à l’aide des API iOS existantes.
Actuellement, si l’utilisateur reçoit un appel entrant sur MonkeyCall et que son i Téléphone est verrouillé, la notification reçue sur l’écran de verrouillage est indistinguishable à partir de tout autre type de notification (comme ceux des applications Messages ou Courrier par exemple).
Si l’utilisateur voulait répondre à l’appel, il faudrait faire glisser la notification MonkeyCall pour ouvrir l’application et entrer son code secret (ou id tactile utilisateur) pour déverrouiller le téléphone avant qu’il puisse accepter l’appel et démarrer la conversation.
L’expérience est tout aussi fastidieuse si le téléphone est déverrouillé. Là encore, l’appel MonkeyCall entrant s’affiche sous la forme d’une bannière de notification standard qui se glisse en haut de l’écran. Étant donné que la notification est temporaire, il peut être facilement manqué par l’utilisateur de les forcer à ouvrir le Centre de notification et de trouver la notification spécifique pour répondre, puis appeler ou rechercher et lancer l’application MonkeyCall manuellement.
Expérience de l’application VoIP CallKit
En implémentant les nouvelles API CallKit dans l’application MonkeyCall, l’expérience de l’utilisateur avec un appel VOIP entrant peut être considérablement améliorée dans iOS 10. Prenons l’exemple de l’utilisateur qui reçoit un appel VOIP lorsque son téléphone est verrouillé à partir de ce qui précède. En implémentant CallKit, l’appel s’affiche sur l’écran de verrouillage de l’i Téléphone, tout comme si l’appel était reçu de l’application intégrée Téléphone, avec l’interface utilisateur native et la fonctionnalité de balayage standard pour répondre.
Là encore, si l’i Téléphone est déverrouillé lorsqu’un appel VOIP MonkeyCall est reçu, la même interface utilisateur native, l’interface utilisateur native et les fonctionnalités de balayage à réponse standard et appuyez pour refuser la fonctionnalité intégrée de l’application Téléphone intégrée est présentée et MonkeyCall a la possibilité de lire une sonnerie personnalisée.
CallKit fournit des fonctionnalités supplémentaires à MonkeyCall, ce qui permet à ses appels VOIP d’interagir avec d’autres types d’appels, d’apparaître dans les listes récentes et favorites intégrées, d’utiliser les fonctionnalités intégrées Do Not Disturb and Block, de démarrer les appels MonkeyCall à partir de Siri et offre la possibilité aux utilisateurs d’affecter des appels MonkeyCall à des personnes de l’application Contacts.
Les sections suivantes couvrent l’architecture CallKit, les flux d’appels entrants et sortants et l’API CallKit en détail.
Architecture CallKit
Dans iOS 10, Apple a adopté CallKit dans tous les services système tels que les appels effectués sur CarPlay, par exemple, sont connus de l’interface utilisateur système via CallKit. Dans l’exemple ci-dessous, étant donné que MonkeyCall adopte CallKit, il est connu du système de la même façon que ces services système intégrés et obtient toutes les mêmes fonctionnalités :
Examinez de plus près l’application MonkeyCall à partir du diagramme ci-dessus. L’application contient tout son code pour communiquer avec son propre réseau et contient ses propres interfaces utilisateur. Il établit des liens dans CallKit pour communiquer avec le système :
Il existe deux interfaces principales dans CallKit que l’application utilise :
CXProvider
- Cela permet à l’application MonkeyCall d’informer le système de toutes les notifications hors bande susceptibles de se produire.CXCallController
- Permet à l’application MonkeyCall d’informer le système d’actions utilisateur locales.
The CXProvider
Comme indiqué ci-dessus, CXProvider
permet à une application d’informer le système de toutes les notifications hors bande susceptibles de se produire. Il s’agit de notifications qui ne se produisent pas en raison d’actions d’utilisateur locales, mais qui se produisent en raison d’événements externes tels que les appels entrants.
Une application doit utiliser les CXProvider
éléments suivants :
- Signalez un appel entrant au système.
- Signalez un appel sortant connecté au système.
- Signalez à l’utilisateur distant la fin de l’appel au système.
Lorsque l’application souhaite communiquer avec le système, elle utilise la CXCallUpdate
classe et quand le système doit communiquer avec l’application, elle utilise la CXAction
classe :
The CXCallController
L’application CXCallController
permet à une application d’informer le système d’actions utilisateur locales telles que l’utilisateur qui démarre un appel VOIP. En implémentant une CXCallController
application, vous pouvez interagir avec d’autres types d’appels dans le système. Par exemple, s’il existe déjà un appel téléphonique actif en cours, CXCallController
peut autoriser l’application VOIP à placer cet appel en attente et à démarrer ou répondre à un appel VOIP.
Une application doit utiliser les CXCallController
éléments suivants :
- Signalez quand l’utilisateur a démarré un appel sortant au système.
- Signalez quand l’utilisateur répond à un appel entrant au système.
- Signalez quand l’utilisateur termine un appel au système.
Lorsque l’application souhaite communiquer les actions de l’utilisateur local au système, elle utilise la CXTransaction
classe :
Implémentation de CallKit
Les sections suivantes montrent comment implémenter CallKit dans une application VOIP Xamarin.iOS. Par exemple, ce document utilise du code à partir de l’application fictive MonkeyCall VOIP. Le code présenté ici représente plusieurs classes de prise en charge, les parties spécifiques de CallKit seront décrites en détail dans les sections suivantes.
La classe ActiveCall
La ActiveCall
classe est utilisée par l’application MonkeyCall pour contenir toutes les informations relatives à un appel VOIP actuellement actif comme suit :
using System;
using CoreFoundation;
using Foundation;
namespace MonkeyCall
{
public class ActiveCall
{
#region Private Variables
private bool isConnecting;
private bool isConnected;
private bool isOnhold;
#endregion
#region Computed Properties
public NSUuid UUID { get; set; }
public bool isOutgoing { get; set; }
public string Handle { get; set; }
public DateTime StartedConnectingOn { get; set;}
public DateTime ConnectedOn { get; set;}
public DateTime EndedOn { get; set; }
public bool IsConnecting {
get { return isConnecting; }
set {
isConnecting = value;
if (isConnecting) StartedConnectingOn = DateTime.Now;
RaiseStartingConnectionChanged ();
}
}
public bool IsConnected {
get { return isConnected; }
set {
isConnected = value;
if (isConnected) {
ConnectedOn = DateTime.Now;
} else {
EndedOn = DateTime.Now;
}
RaiseConnectedChanged ();
}
}
public bool IsOnHold {
get { return isOnhold; }
set {
isOnhold = value;
}
}
#endregion
#region Constructors
public ActiveCall ()
{
}
public ActiveCall (NSUuid uuid, string handle, bool outgoing)
{
// Initialize
this.UUID = uuid;
this.Handle = handle;
this.isOutgoing = outgoing;
}
#endregion
#region Public Methods
public void StartCall (ActiveCallbackDelegate completionHandler)
{
// Simulate the call starting successfully
completionHandler (true);
// Simulate making a starting and completing a connection
DispatchQueue.MainQueue.DispatchAfter (new DispatchTime(DispatchTime.Now, 3000), () => {
// Note that the call is starting
IsConnecting = true;
// Simulate pause before connecting
DispatchQueue.MainQueue.DispatchAfter (new DispatchTime (DispatchTime.Now, 1500), () => {
// Note that the call has connected
IsConnecting = false;
IsConnected = true;
});
});
}
public void AnswerCall (ActiveCallbackDelegate completionHandler)
{
// Simulate the call being answered
IsConnected = true;
completionHandler (true);
}
public void EndCall (ActiveCallbackDelegate completionHandler)
{
// Simulate the call ending
IsConnected = false;
completionHandler (true);
}
#endregion
#region Events
public delegate void ActiveCallbackDelegate (bool successful);
public delegate void ActiveCallStateChangedDelegate (ActiveCall call);
public event ActiveCallStateChangedDelegate StartingConnectionChanged;
internal void RaiseStartingConnectionChanged ()
{
if (this.StartingConnectionChanged != null) this.StartingConnectionChanged (this);
}
public event ActiveCallStateChangedDelegate ConnectedChanged;
internal void RaiseConnectedChanged ()
{
if (this.ConnectedChanged != null) this.ConnectedChanged (this);
}
#endregion
}
}
ActiveCall
contient plusieurs propriétés qui définissent l’état de l’appel et deux événements qui peuvent être déclenchés lorsque l’état de l’appel change. Étant donné qu’il s’agit d’un exemple uniquement, il existe trois méthodes utilisées pour simuler le démarrage, répondre et mettre fin à un appel.
La classe StartCallRequest
La StartCallRequest
classe statique fournit quelques méthodes d’assistance qui seront utilisées lors de l’utilisation d’appels sortants :
using System;
using Foundation;
using Intents;
namespace MonkeyCall
{
public static class StartCallRequest
{
public static string URLScheme {
get { return "monkeycall"; }
}
public static string ActivityType {
get { return INIntentIdentifier.StartAudioCall.GetConstant ().ToString (); }
}
public static string CallHandleFromURL (NSUrl url)
{
// Is this a MonkeyCall handle?
if (url.Scheme == URLScheme) {
// Yes, return host
return url.Host;
} else {
// Not handled
return null;
}
}
public static string CallHandleFromActivity (NSUserActivity activity)
{
// Is this a start call activity?
if (activity.ActivityType == ActivityType) {
// Yes, trap any errors
try {
// Get first contact
var interaction = activity.GetInteraction ();
var startAudioCallIntent = interaction.Intent as INStartAudioCallIntent;
var contact = startAudioCallIntent.Contacts [0];
// Get the person handle
return contact.PersonHandle.Value;
} catch {
// Error, report null
return null;
}
} else {
// Not handled
return null;
}
}
}
}
Les CallHandleFromURL
classes et CallHandleFromActivity
les classes sont utilisées dans AppDelegate pour obtenir le handle de contact de la personne appelée dans un appel sortant. Pour plus d’informations, consultez la section Gestion des appels sortants ci-dessous.
La classe ActiveCallManager
La ActiveCallManager
classe gère tous les appels ouverts dans l’application MonkeyCall.
using System;
using System.Collections.Generic;
using Foundation;
using CallKit;
namespace MonkeyCall
{
public class ActiveCallManager
{
#region Private Variables
private CXCallController CallController = new CXCallController ();
#endregion
#region Computed Properties
public List<ActiveCall> Calls { get; set; }
#endregion
#region Constructors
public ActiveCallManager ()
{
// Initialize
this.Calls = new List<ActiveCall> ();
}
#endregion
#region Private Methods
private void SendTransactionRequest (CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction (transaction, (error) => {
// Was there an error?
if (error == null) {
// No, report success
Console.WriteLine ("Transaction request sent successfully.");
} else {
// Yes, report error
Console.WriteLine ("Error requesting transaction: {0}", error);
}
});
}
#endregion
#region Public Methods
public ActiveCall FindCall (NSUuid uuid)
{
// Scan for requested call
foreach (ActiveCall call in Calls) {
if (call.UUID.Equals(uuid)) return call;
}
// Not found
return null;
}
public void StartCall (string contact)
{
// Build call action
var handle = new CXHandle (CXHandleType.Generic, contact);
var startCallAction = new CXStartCallAction (new NSUuid (), handle);
// Create transaction
var transaction = new CXTransaction (startCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void EndCall (ActiveCall call)
{
// Build action
var endCallAction = new CXEndCallAction (call.UUID);
// Create transaction
var transaction = new CXTransaction (endCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void PlaceCallOnHold (ActiveCall call)
{
// Build action
var holdCallAction = new CXSetHeldCallAction (call.UUID, true);
// Create transaction
var transaction = new CXTransaction (holdCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void RemoveCallFromOnHold (ActiveCall call)
{
// Build action
var holdCallAction = new CXSetHeldCallAction (call.UUID, false);
// Create transaction
var transaction = new CXTransaction (holdCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
#endregion
}
}
Là encore, étant donné qu’il s’agit d’une simulation uniquement, la ActiveCallManager
seule conserve une collection d’objets ActiveCall
et a une routine pour trouver un appel donné par sa UUID
propriété. Il inclut également des méthodes permettant de démarrer, de terminer et de modifier l’état d’attente d’un appel sortant. Pour plus d’informations, consultez la section Gestion des appels sortants ci-dessous.
La classe ProviderDelegate
Comme indiqué ci-dessus, une CXProvider
communication bidirectionnelle entre l’application et le système pour les notifications hors bande. Le développeur doit fournir un callKit personnalisé CXProviderDelegate
et l’attacher à l’application CXProvider
pour gérer les événements CallKit hors bande. MonkeyCall utilise les éléments suivants CXProviderDelegate
:
using System;
using Foundation;
using CallKit;
using UIKit;
namespace MonkeyCall
{
public class ProviderDelegate : CXProviderDelegate
{
#region Computed Properties
public ActiveCallManager CallManager { get; set;}
public CXProviderConfiguration Configuration { get; set; }
public CXProvider Provider { get; set; }
#endregion
#region Constructors
public ProviderDelegate (ActiveCallManager callManager)
{
// Save connection to call manager
CallManager = callManager;
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
}
#endregion
#region Override Methods
public override void DidReset (CXProvider provider)
{
// Remove all calls
CallManager.Calls.Clear ();
}
public override void PerformStartCallAction (CXProvider provider, CXStartCallAction action)
{
// Create new call record
var activeCall = new ActiveCall (action.CallUuid, action.CallHandle.Value, true);
// Monitor state changes
activeCall.StartingConnectionChanged += (call) => {
if (call.isConnecting) {
// Inform system that the call is starting
Provider.ReportConnectingOutgoingCall (call.UUID, call.StartedConnectingOn.ToNSDate());
}
};
activeCall.ConnectedChanged += (call) => {
if (call.isConnected) {
// Inform system that the call has connected
provider.ReportConnectedOutgoingCall (call.UUID, call.ConnectedOn.ToNSDate ());
}
};
// Start call
activeCall.StartCall ((successful) => {
// Was the call able to be started?
if (successful) {
// Yes, inform the system
action.Fulfill ();
// Add call to manager
CallManager.Calls.Add (activeCall);
} else {
// No, inform system
action.Fail ();
}
});
}
public override void PerformAnswerCallAction (CXProvider provider, CXAnswerCallAction action)
{
// Find requested call
var call = CallManager.FindCall (action.CallUuid);
// Found?
if (call == null) {
// No, inform system and exit
action.Fail ();
return;
}
// Attempt to answer call
call.AnswerCall ((successful) => {
// Was the call successfully answered?
if (successful) {
// Yes, inform system
action.Fulfill ();
} else {
// No, inform system
action.Fail ();
}
});
}
public override void PerformEndCallAction (CXProvider provider, CXEndCallAction action)
{
// Find requested call
var call = CallManager.FindCall (action.CallUuid);
// Found?
if (call == null) {
// No, inform system and exit
action.Fail ();
return;
}
// Attempt to answer call
call.EndCall ((successful) => {
// Was the call successfully answered?
if (successful) {
// Remove call from manager's queue
CallManager.Calls.Remove (call);
// Yes, inform system
action.Fulfill ();
} else {
// No, inform system
action.Fail ();
}
});
}
public override void PerformSetHeldCallAction (CXProvider provider, CXSetHeldCallAction action)
{
// Find requested call
var call = CallManager.FindCall (action.CallUuid);
// Found?
if (call == null) {
// No, inform system and exit
action.Fail ();
return;
}
// Update hold status
call.isOnHold = action.OnHold;
// Inform system of success
action.Fulfill ();
}
public override void TimedOutPerformingAction (CXProvider provider, CXAction action)
{
// Inform user that the action has timed out
}
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the calls audio session here
}
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// related audio
}
#endregion
#region Public Methods
public void ReportIncomingCall (NSUuid uuid, string handle)
{
// Create update to describe the incoming call and caller
var update = new CXCallUpdate ();
update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);
// Report incoming call to system
Provider.ReportNewIncomingCall (uuid, update, (error) => {
// Was the call accepted
if (error == null) {
// Yes, report to call manager
CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
} else {
// Report error to user here
Console.WriteLine ("Error: {0}", error);
}
});
}
#endregion
}
}
Lorsqu’une instance de ce délégué est créée, elle est passée à celle ActiveCallManager
qu’elle utilisera pour gérer n’importe quelle activité d’appel. Ensuite, il définit les types de handles (CXHandleType
) auxquels le CXProvider
répond :
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
Il obtient l’image de modèle qui sera appliquée à l’icône de l’application lorsqu’un appel est en cours :
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
Ces valeurs sont regroupées dans un CXProviderConfiguration
objet qui sera utilisé pour configurer les CXProvider
éléments suivants :
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
Le délégué crée ensuite une nouvelle CXProvider
configuration avec ces configurations et s’y attache :
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
Lorsque vous utilisez CallKit, l’application ne crée plus et ne gère plus ses propres sessions audio, au lieu de cela, elle doit configurer et utiliser une session audio que le système va créer et gérer pour elle.
S’il s’agissait d’une application réelle, la DidActivateAudioSession
méthode serait utilisée pour démarrer l’appel avec une préconfigurée AVAudioSession
fournie par le système :
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
Il utilise également la DidDeactivateAudioSession
méthode pour finaliser et libérer sa connexion à la session audio fournie par le système :
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// releated audio
}
Le reste du code sera abordé en détail dans les sections qui suivent.
Classe AppDelegate
MonkeyCall utilise l’AppDelegate pour contenir les instances de l’application ActiveCallManager
et CXProviderDelegate
qui seront utilisées dans l’application :
using Foundation;
using UIKit;
using Intents;
using System;
namespace MonkeyCall
{
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
#region Constructors
public override UIWindow Window { get; set; }
public ActiveCallManager CallManager { get; set; }
public ProviderDelegate CallProviderDelegate { get; set; }
#endregion
#region Override Methods
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
// Initialize the call handlers
CallManager = new ActiveCallManager ();
CallProviderDelegate = new ProviderDelegate (CallManager);
return true;
}
public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options)
{
// Get handle from url
var handle = StartCallRequest.CallHandleFromURL (url);
// Found?
if (handle == null) {
// No, report to system
Console.WriteLine ("Unable to get call handle from URL: {0}", url);
return false;
} else {
// Yes, start call and inform system
CallManager.StartCall (handle);
return true;
}
}
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
var handle = StartCallRequest.CallHandleFromActivity (userActivity);
// Found?
if (handle == null) {
// No, report to system
Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
return false;
} else {
// Yes, start call and inform system
CallManager.StartCall (handle);
return true;
}
}
...
#endregion
}
}
Les OpenUrl
méthodes de remplacement et ContinueUserActivity
de substitution sont utilisées lorsque l’application traite un appel sortant. Pour plus d’informations, consultez la section Gestion des appels sortants ci-dessous.
Gestion des appels entrants
Il existe plusieurs états et processus qu’un appel VOIP entrant peut passer pendant un flux de travail d’appel entrant classique, par exemple :
- Informer l’utilisateur (et le système) qu’un appel entrant existe.
- Réception de notification lorsque l’utilisateur souhaite répondre à l’appel et initialiser l’appel avec l’autre utilisateur.
- Informez le système et le réseau de communication lorsque l’utilisateur souhaite mettre fin à l’appel actuel.
Les sections suivantes examinent en détail comment une application peut utiliser CallKit pour gérer le flux de travail d’appel entrant, à nouveau à l’aide de l’application VoIP MonkeyCall comme exemple.
Informer l’utilisateur de l’appel entrant
Lorsqu’un utilisateur distant a démarré une conversation VOIP avec l’utilisateur local, les événements suivants se produisent :
- L’application reçoit une notification de son réseau de communications indiquant qu’il existe un appel VOIP entrant.
- L’application utilise la
CXProvider
commande pour envoyer unCXCallUpdate
message au système qui l’informe de l’appel. - Le système publie l’appel à l’interface utilisateur système, aux services système et à toutes les autres applications VOIP à l’aide de CallKit.
Par exemple, dans :CXProviderDelegate
public void ReportIncomingCall (NSUuid uuid, string handle)
{
// Create update to describe the incoming call and caller
var update = new CXCallUpdate ();
update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);
// Report incoming call to system
Provider.ReportNewIncomingCall (uuid, update, (error) => {
// Was the call accepted
if (error == null) {
// Yes, report to call manager
CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
} else {
// Report error to user here
Console.WriteLine ("Error: {0}", error);
}
});
}
Ce code crée une instance CXCallUpdate
et attache un handle à celui-ci qui identifie l’appelant. Ensuite, il utilise la ReportNewIncomingCall
méthode de la CXProvider
classe pour informer le système de l’appel. Si elle réussit, l’appel est ajouté à la collection d’appels actifs de l’application, si ce n’est pas le cas, l’erreur doit être signalée à l’utilisateur.
Utilisateur répondant à un appel entrant
Si l’utilisateur souhaite répondre à l’appel VOIP entrant, les événements suivants se produisent :
- L’interface utilisateur système informe le système que l’utilisateur souhaite répondre à l’appel VOIP.
- Le système envoie un
CXAnswerCallAction
message à l’application pour l’informer de l’intentionCXProvider
de réponse. - L’application informe son réseau de communication que l’utilisateur répond à l’appel et que l’appel VOIP se poursuit comme d’habitude.
Par exemple, dans :CXProviderDelegate
public override void PerformAnswerCallAction (CXProvider provider, CXAnswerCallAction action)
{
// Find requested call
var call = CallManager.FindCall (action.CallUuid);
// Found?
if (call == null) {
// No, inform system and exit
action.Fail ();
return;
}
// Attempt to answer call
call.AnswerCall ((successful) => {
// Was the call successfully answered?
if (successful) {
// Yes, inform system
action.Fulfill ();
} else {
// No, inform system
action.Fail ();
}
});
}
Ce code recherche d’abord l’appel donné dans sa liste d’appels actifs. Si l’appel est introuvable, le système est averti et la méthode se ferme. Si elle est trouvée, la AnswerCall
méthode de la ActiveCall
classe est appelée pour démarrer l’appel et le système est des informations s’il réussit ou échoue.
Utilisateur terminant l’appel entrant
Si l’utilisateur souhaite arrêter l’appel à partir de l’interface utilisateur de l’application, les éléments suivants se produisent :
- L’application crée
CXEndCallAction
un ensemble quiCXTransaction
est envoyé au système pour l’informer que l’appel se termine. - Le système vérifie l’intention d’appel final et renvoie l’application
CXEndCallAction
via leCXProvider
. - L’application informe ensuite son réseau de communication que l’appel se termine.
Par exemple, dans :CXProviderDelegate
public override void PerformEndCallAction (CXProvider provider, CXEndCallAction action)
{
// Find requested call
var call = CallManager.FindCall (action.CallUuid);
// Found?
if (call == null) {
// No, inform system and exit
action.Fail ();
return;
}
// Attempt to answer call
call.EndCall ((successful) => {
// Was the call successfully answered?
if (successful) {
// Remove call from manager's queue
CallManager.Calls.Remove (call);
// Yes, inform system
action.Fulfill ();
} else {
// No, inform system
action.Fail ();
}
});
}
Ce code recherche d’abord l’appel donné dans sa liste d’appels actifs. Si l’appel est introuvable, le système est averti et la méthode se ferme. Si elle est trouvée, la EndCall
méthode de la ActiveCall
classe est appelée pour mettre fin à l’appel et le système est des informations s’il réussit ou échoue. En cas de réussite, l’appel est supprimé de la collection d’appels actifs.
Gestion de plusieurs appels
La plupart des applications VOIP peuvent gérer plusieurs appels à la fois. Par exemple, s’il existe actuellement un appel VOIP actif et que l’application reçoit une notification indiquant qu’il existe un nouvel appel entrant, l’utilisateur peut suspendre ou raccrocher sur le premier appel pour répondre au deuxième.
Dans la situation ci-dessus, le système envoie un CXTransaction
message à l’application qui inclut une liste de plusieurs actions (telles que le CXEndCallAction
et le CXAnswerCallAction
). Toutes ces actions devront être remplies individuellement, afin que le système puisse mettre à jour l’interface utilisateur de manière appropriée.
Gestion des appels sortants
Si l’utilisateur appuie sur une entrée de la liste Récents (dans l’application Téléphone), par exemple, à partir d’un appel appartenant à l’application, il est envoyé une intention d’appel de démarrage par le système :
- L’application crée une action d’appel de démarrage en fonction de l’intention d’appel de démarrage qu’elle a reçue du système.
- L’application utilisera l’application
CXCallController
pour demander l’action Démarrer l’appel à partir du système. - Si le système accepte l’action, il est retourné à l’application via le
XCProvider
délégué. - L’application démarre l’appel sortant avec son réseau de communication.
Pour plus d’informations sur les intentions, consultez notre documentation sur les extensions d’interface utilisateur intentions et intentions.
Cycle de vie des appels sortants
Lorsque vous utilisez CallKit et un appel sortant, l’application doit informer le système des événements de cycle de vie suivants :
- Démarrage : informez le système qu’un appel sortant est sur le point de démarrer.
- Démarré : informez le système qu’un appel sortant a démarré.
- Connecter ing : informez le système que l’appel sortant se connecte.
- Connecter ed - Informez l’appel sortant et que les deux parties peuvent parler maintenant.
Par exemple, le code suivant démarre un appel sortant :
private CXCallController CallController = new CXCallController ();
...
private void SendTransactionRequest (CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction (transaction, (error) => {
// Was there an error?
if (error == null) {
// No, report success
Console.WriteLine ("Transaction request sent successfully.");
} else {
// Yes, report error
Console.WriteLine ("Error requesting transaction: {0}", error);
}
});
}
public void StartCall (string contact)
{
// Build call action
var handle = new CXHandle (CXHandleType.Generic, contact);
var startCallAction = new CXStartCallAction (new NSUuid (), handle);
// Create transaction
var transaction = new CXTransaction (startCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
Il crée un CXHandle
fichier et l’utilise pour configurer un CXStartCallAction
élément qui est regroupé dans un CXTransaction
système envoyé au système à l’aide de la RequestTransaction
méthode de la CXCallController
classe. En appelant la RequestTransaction
méthode, le système peut placer tous les appels existants en attente, quelle que soit la source (Téléphone application, FaceTime, VOIP, etc.), avant le démarrage du nouvel appel.
La demande de démarrage d’un appel VOIP sortant peut provenir de plusieurs sources différentes, telles que Siri, une entrée sur un contact carte (dans l’application Contacts) ou à partir de la liste Récents (dans l’application Téléphone). Dans ces situations, l’application est envoyée à l’intérieur d’une NSUserActivity
intention d’appel de démarrage et l’ApplicationDelegate doit la gérer :
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
var handle = StartCallRequest.CallHandleFromActivity (userActivity);
// Found?
if (handle == null) {
// No, report to system
Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
return false;
} else {
// Yes, start call and inform system
CallManager.StartCall (handle);
return true;
}
}
Ici, la CallHandleFromActivity
méthode de la classe StartCallRequest
d’assistance est utilisée pour obtenir le handle à la personne appelée (voir la classe StartCallRequest ci-dessus).
La PerformStartCallAction
méthode de la classe ProviderDelegate est utilisée pour enfin démarrer l’appel sortant réel et informer le système de son cycle de vie :
public override void PerformStartCallAction (CXProvider provider, CXStartCallAction action)
{
// Create new call record
var activeCall = new ActiveCall (action.CallUuid, action.CallHandle.Value, true);
// Monitor state changes
activeCall.StartingConnectionChanged += (call) => {
if (call.IsConnecting) {
// Inform system that the call is starting
Provider.ReportConnectingOutgoingCall (call.UUID, call.StartedConnectingOn.ToNSDate());
}
};
activeCall.ConnectedChanged += (call) => {
if (call.IsConnected) {
// Inform system that the call has connected
Provider.ReportConnectedOutgoingCall (call.UUID, call.ConnectedOn.ToNSDate ());
}
};
// Start call
activeCall.StartCall ((successful) => {
// Was the call able to be started?
if (successful) {
// Yes, inform the system
action.Fulfill ();
// Add call to manager
CallManager.Calls.Add (activeCall);
} else {
// No, inform system
action.Fail ();
}
});
}
Il crée une instance de la ActiveCall
classe (pour contenir des informations sur l’appel en cours) et remplit la personne appelée. Les StartingConnectionChanged
événements et ConnectedChanged
les événements sont utilisés pour surveiller et signaler le cycle de vie des appels sortants. L’appel est démarré et le système a informé que l’action a été exécutée.
Fin d’un appel sortant
Une fois l’utilisateur terminé avec un appel sortant et souhaite le terminer, le code suivant peut être utilisé :
private CXCallController CallController = new CXCallController ();
...
private void SendTransactionRequest (CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction (transaction, (error) => {
// Was there an error?
if (error == null) {
// No, report success
Console.WriteLine ("Transaction request sent successfully.");
} else {
// Yes, report error
Console.WriteLine ("Error requesting transaction: {0}", error);
}
});
}
public void EndCall (ActiveCall call)
{
// Build action
var endCallAction = new CXEndCallAction (call.UUID);
// Create transaction
var transaction = new CXTransaction (endCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
Si vous créez un CXEndCallAction
UUID de l’appel à la fin, le regroupe dans un CXTransaction
élément envoyé au système à l’aide de la RequestTransaction
méthode de la CXCallController
classe.
Détails supplémentaires de CallKit
Cette section décrit quelques détails supplémentaires que le développeur devra prendre en compte lors de l’utilisation de CallKit, par exemple :
- Configuration du fournisseur
- Erreurs en relation avec les actions
- Restrictions système
- VOIP Audio
Configuration du fournisseur
La configuration du fournisseur permet à une application VOIP iOS 10 de personnaliser l’expérience utilisateur (à l’intérieur de l’interface utilisateur in-call native) lors de l’utilisation de CallKit.
Une application peut effectuer les types de personnalisations suivants :
- Affichez un nom localisé.
- Activer la prise en charge des appels vidéo.
- Personnalisez les boutons de l’interface utilisateur in-call en présentant son propre icône d’image de modèle. L’interaction utilisateur avec des boutons personnalisés est envoyée directement à l’application à traiter.
Erreurs d’action
Les applications VOIP iOS 10 utilisant CallKit doivent gérer l’échec des actions correctement et garder l’utilisateur informé de l’état d’action à tout moment.
Prenez en compte l’exemple suivant :
- L’application a reçu une action Démarrer l’appel et a commencé le processus d’initialisation d’un nouvel appel VOIP avec son réseau de communication.
- En raison d’une fonctionnalité de communication réseau limitée ou inexistante, cette connexion échoue.
- L’application doit renvoyer le message de restauration automatique à l’action Démarrer l’appel (
Action.Fail()
) pour informer le système de l’échec. - Cela permet au système d’informer l’utilisateur de l’état de l’appel. Par exemple, pour afficher l’interface utilisateur d’échec d’appel.
En outre, une application VOIP iOS 10 doit répondre aux erreurs de délai d’expiration qui peuvent se produire lorsqu’une action attendue ne peut pas être traitée dans un délai donné. Chaque type d’action fourni par CallKit a une valeur de délai d’expiration maximale associée. Ces valeurs de délai d’attente garantissent que toute action CallKit demandée par l’utilisateur est gérée de manière réactive, ce qui permet de maintenir le système d’exploitation fluide et réactif ainsi.
Il existe plusieurs méthodes sur le délégué du fournisseur (CXProviderDelegate
) qui doivent également être remplacées pour gérer correctement ces situations de délai d’attente.
Restrictions système
En fonction de l’état actuel de l’appareil iOS exécutant l’application VOIP iOS 10, certaines restrictions système peuvent être appliquées.
Par exemple, un appel VOIP entrant peut être limité par le système si :
- L’appel de la personne se trouve dans la liste des appelants bloqués de l’utilisateur.
- L’appareil iOS de l’utilisateur est en mode Ne pas déranger.
Si un appel VOIP est limité par l’une de ces situations, utilisez le code suivant pour le gérer :
public class ProviderDelegate : CXProviderDelegate
{
...
public void ReportIncomingCall (NSUuid uuid, string handle)
{
// Create update to describe the incoming call and caller
var update = new CXCallUpdate ();
update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);
// Report incoming call to system
Provider.ReportNewIncomingCall (uuid, update, (error) => {
// Was the call accepted
if (error == null) {
// Yes, report to call manager
CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
} else {
// Report error to user here
if (error.Code == (int)CXErrorCodeIncomingCallError.CallUuidAlreadyExists) {
// Handle duplicate call ID
} else if (error.Code == (int)CXErrorCodeIncomingCallError.FilteredByBlockList) {
// Handle call from blocked user
} else if (error.Code == (int)CXErrorCodeIncomingCallError.FilteredByDoNotDisturb) {
// Handle call while in do-not-disturb mode
} else {
// Handle unknown error
}
}
});
}
}
Audio VOIP
CallKit offre plusieurs avantages pour gérer les ressources audio qu’une application VOIP iOS 10 nécessite lors d’un appel VOIP en direct. L’un des principaux avantages est que la session audio de l’application aura des priorités élevées lors de l’exécution dans iOS 10. Il s’agit du même niveau de priorité que les applications intégrées Téléphone et FaceTime, et ce niveau de priorité amélioré empêche les autres applications en cours d’exécution d’interrompre la session audio de l’application VOIP.
En outre, CallKit a accès à d’autres indicateurs de routage audio qui peuvent améliorer les performances et acheminer intelligemment l’audio VOIP vers des appareils de sortie spécifiques pendant un appel en direct en fonction des préférences utilisateur et des états des appareils. Par exemple, sur la base d’appareils attachés tels que des écouteurs bluetooth, une connexion CarPlay active ou des paramètres d’accessibilité.
Pendant le cycle de vie d’un appel VOIP classique à l’aide de CallKit, l’application doit configurer le flux audio fourni par CallKit. Examinez l’exemple suivant :
- Une action Démarrer l’appel est reçue par l’application pour répondre à un appel entrant.
- Avant que cette action ne soit remplie par l’application, elle fournit la configuration requise pour son
AVAudioSession
. - L’application informe le système que l’action a été remplie.
- Avant la connexion de l’appel, CallKit fournit une priorité
AVAudioSession
élevée correspondant à la configuration demandée par l’application. L’application sera avertie par le biais de laDidActivateAudioSession
méthode de sonCXProviderDelegate
.
Utilisation des extensions d’annuaire d’appels
Lorsque vous utilisez CallKit, les extensions d’annuaire des appels permettent d’ajouter des numéros d’appel bloqués et d’identifier les numéros spécifiques à une application VOIP donnée aux contacts de l’application Contact sur l’appareil iOS.
Implémentation d’une extension d’annuaire d’appels
Pour implémenter une extension d’annuaire d’appels dans une application Xamarin.iOS, procédez comme suit :
Ouvrez la solution de l’application dans Visual Studio pour Mac.
Cliquez avec le bouton droit sur le nom de la solution dans le Explorateur de solutions, puis sélectionnez Ajouter>un nouveau projet.
Sélectionnez Extensions>d’appel d’extensions iOS>, puis cliquez sur le bouton Suivant :
Entrez un nom pour l’extension, puis cliquez sur le bouton Suivant :
Ajustez le nom du projet et/ou le nom de la solution si nécessaire, puis cliquez sur le bouton Créer :
Cela ajoute une CallDirectoryHandler.cs
classe au projet qui ressemble à ce qui suit :
using System;
using Foundation;
using CallKit;
namespace MonkeyCallDirExtension
{
[Register ("CallDirectoryHandler")]
public class CallDirectoryHandler : CXCallDirectoryProvider, ICXCallDirectoryExtensionContextDelegate
{
#region Constructors
protected CallDirectoryHandler (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
#endregion
#region Override Methods
public override void BeginRequest (CXCallDirectoryExtensionContext context)
{
context.Delegate = this;
if (!AddBlockingPhoneNumbers (context)) {
Console.WriteLine ("Unable to add blocking phone numbers");
var error = new NSError (new NSString ("CallDirectoryHandler"), 1, null);
context.CancelRequest (error);
return;
}
if (!AddIdentificationPhoneNumbers (context)) {
Console.WriteLine ("Unable to add identification phone numbers");
var error = new NSError (new NSString ("CallDirectoryHandler"), 2, null);
context.CancelRequest (error);
return;
}
context.CompleteRequest (null);
}
#endregion
#region Private Methods
private bool AddBlockingPhoneNumbers (CXCallDirectoryExtensionContext context)
{
// Retrieve phone numbers to block from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
//
// Numbers must be provided in numerically ascending order.
long [] phoneNumbers = { 14085555555, 18005555555 };
foreach (var phoneNumber in phoneNumbers)
context.AddBlockingEntry (phoneNumber);
return true;
}
private bool AddIdentificationPhoneNumbers (CXCallDirectoryExtensionContext context)
{
// Retrieve phone numbers to identify and their identification labels from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
//
// Numbers must be provided in numerically ascending order.
long [] phoneNumbers = { 18775555555, 18885555555 };
string [] labels = { "Telemarketer", "Local business" };
for (var i = 0; i < phoneNumbers.Length; i++) {
long phoneNumber = phoneNumbers [i];
string label = labels [i];
context.AddIdentificationEntry (phoneNumber, label);
}
return true;
}
#endregion
#region Public Methods
public void RequestFailed (CXCallDirectoryExtensionContext extensionContext, NSError error)
{
// An error occurred while adding blocking or identification entries, check the NSError for details.
// For Call Directory error codes, see the CXErrorCodeCallDirectoryManagerError enum.
//
// This may be used to store the error details in a location accessible by the extension's containing app, so that the
// app may be notified about errors which occurred while loading data even if the request to load data was initiated by
// the user in Settings instead of via the app itself.
}
#endregion
}
}
La BeginRequest
méthode du gestionnaire d’annuaires d’appels doit être modifiée pour fournir les fonctionnalités requises. Dans le cas de l’exemple ci-dessus, il tente de définir la liste des numéros bloqués et disponibles dans la base de données de contacts de l’application VOIP. Si l’une des demandes échoue pour une raison quelconque, créez-en une NSError
pour décrire l’échec et transmettez-la à la CancelRequest
méthode de la CXCallDirectoryExtensionContext
classe.
Pour définir les nombres bloqués, utilisez la AddBlockingEntry
méthode de la CXCallDirectoryExtensionContext
classe. Les nombres fournis à la méthode doivent être dans un ordre croissant numérique. Pour optimiser les performances et l’utilisation de la mémoire lorsqu’il existe de nombreux numéros de téléphone, envisagez de charger uniquement un sous-ensemble de nombres à un moment donné et d’utiliser des pools de rééditions automatiques pour libérer des objets alloués pendant chaque lot de nombres chargés.
Pour informer l’application Contact des numéros de contact connus de l’application VOIP, utilisez la AddIdentificationEntry
méthode de la CXCallDirectoryExtensionContext
classe et fournissez le numéro et une étiquette d’identification. Là encore, les nombres fournis à la méthode doivent être dans un ordre croissant numérique. Pour optimiser les performances et l’utilisation de la mémoire lorsqu’il existe de nombreux numéros de téléphone, envisagez de charger uniquement un sous-ensemble de nombres à un moment donné et d’utiliser des pools de rééditions automatiques pour libérer des objets alloués pendant chaque lot de nombres chargés.
Résumé
Cet article a abordé la nouvelle API CallKit publiée par Apple dans iOS 10 et comment l’implémenter dans les applications VOIP Xamarin.iOS. Il a montré comment CallKit permet à une application d’intégrer dans le système iOS, comment elle fournit la parité des fonctionnalités avec les applications intégrées (telles que Téléphone) et comment elle augmente la visibilité d’une application dans iOS dans des emplacements tels que le verrouillage et les écrans d’accueil, via les interactions Siri et via les applications Contacts.