CallKit in Xamarin.iOS
La nuova API CallKit in iOS 10 consente alle app VOIP di integrarsi con l'interfaccia utente i Telefono e offrire un'interfaccia e un'esperienza familiari all'utente finale. Con questa API gli utenti possono visualizzare e interagire con le chiamate VOIP dalla schermata di blocco del dispositivo iOS e gestire i contatti usando le visualizzazioni Preferiti e Recenti dell'app Telefono.
Informazioni su CallKit
Secondo Apple, CallKit è un nuovo framework che eleva le app Voice Over IP (VOIP) di terze parti a un'esperienza di prima parte in iOS 10. L'API CallKit consente alle app VOIP di integrarsi con l'interfaccia utente i Telefono e offrire un'interfaccia e un'esperienza familiari all'utente finale. Proprio come l'app di Telefono predefinita, un utente può visualizzare e interagire con le chiamate VOIP dalla schermata di blocco del dispositivo iOS e gestire i contatti usando le visualizzazioni Preferiti e Recenti dell'app Telefono.
Inoltre, l'API CallKit consente di creare estensioni dell'app che possono associare un numero di telefono a un nome (ID chiamante) o indicare al sistema quando un numero deve essere bloccato (blocco delle chiamate).
Esperienza dell'app VOIP esistente
Prima di discutere della nuova API CallKit e delle sue capacità, esaminare l'esperienza utente corrente con un'app VOIP di terze parti in iOS 9 (e minore) usando un'app VOIP fittizia denominata MonkeyCall. MonkeyCall è una semplice app che consente all'utente di inviare e ricevere chiamate VOIP usando le API iOS esistenti.
Attualmente, se l'utente riceve una chiamata in arrivo su MonkeyCall e il relativo i Telefono è bloccato, la notifica ricevuta nella schermata di blocco è indistinguibile da qualsiasi altro tipo di notifica (ad esempio quelli delle app Messaggi o Posta elettronica).
Se l'utente vuole rispondere alla chiamata, dovrà far scorrere la notifica MonkeyCall per aprire l'app e immettere il passcode (o l'ID tocco dell'utente) per sbloccare il telefono prima di poter accettare la chiamata e avviare la conversazione.
L'esperienza è ugualmente complessa se il telefono è sbloccato. Anche in questo caso, la chiamata MonkeyCall in ingresso viene visualizzata come banner di notifica standard che scorre dalla parte superiore dello schermo. Poiché la notifica è temporanea, può essere facilmente persa dall'utente forzandoli ad aprire il Centro notifiche e trovare la notifica specifica per rispondere, quindi chiamare o trovare e avviare manualmente l'app MonkeyCall.
Esperienza dell'app VOIP CallKit
Implementando le nuove API CallKit nell'app MonkeyCall, l'esperienza dell'utente con una chiamata VOIP in ingresso può essere notevolmente migliorata in iOS 10. Si prenda l'esempio dell'utente che riceve una chiamata VOIP quando il telefono è bloccato da sopra. Implementando CallKit, la chiamata verrà visualizzata nella schermata di blocco di i Telefono, proprio come se la chiamata venisse ricevuta dall'app Telefono predefinita, con l'interfaccia utente a schermo intero, l'interfaccia utente nativa e la funzionalità di scorrimento rapido a risposta standard.
Anche in questo caso, se l'i Telefono viene sbloccato quando viene ricevuta una chiamata VOIP MonkeyCall, la stessa interfaccia utente nativa a schermo intero e la funzionalità standard di scorrimento rapido alla risposta e tap-to-decline dell'app predefinita Telefono viene presentata e MonkeyCall ha la possibilità di riprodurre una suoneria personalizzata.
CallKit offre funzionalità aggiuntive a MonkeyCall, consentendo alle chiamate VOIP di interagire con altri tipi di chiamate, di apparire negli elenchi Recenti e Preferiti predefiniti, per usare le funzionalità predefinite Do Not Disturb e Block, avviare le chiamate MonkeyCall da Siri e offre agli utenti la possibilità di assegnare chiamate MonkeyCall alle persone nell'app Contatti.
Le sezioni seguenti illustrano in dettaglio l'architettura callkit, i flussi di chiamate in ingresso e in uscita e l'API CallKit.
Architettura di CallKit
In iOS 10 Apple ha adottato CallKit in tutti i servizi di sistema, in modo che le chiamate effettuate su CarPlay, ad esempio, siano note all'interfaccia utente di sistema tramite CallKit. Nell'esempio riportato di seguito, poiché MonkeyCall adotta CallKit, è noto al sistema nello stesso modo di questi servizi di sistema predefiniti e ottiene tutte le stesse funzionalità:
Esaminare più in dettaglio l'app MonkeyCall del diagramma precedente. L'app contiene tutto il codice per comunicare con la propria rete e contiene le proprie interfacce utente. Collegamenti in CallKit per comunicare con il sistema:
Esistono due interfacce principali in CallKit usate dall'app:
CXProvider
- Ciò consente all'app MonkeyCall di informare il sistema di eventuali notifiche fuori banda che potrebbero verificarsi.CXCallController
- Consente all'app MonkeyCall di informare il sistema delle azioni dell'utente locale.
The CXProvider
Come indicato in precedenza, CXProvider
consente a un'app di informare il sistema di eventuali notifiche fuori banda che potrebbero verificarsi. Si tratta di una notifica che non si verifica a causa di azioni dell'utente locale, ma che si verificano a causa di eventi esterni, ad esempio le chiamate in ingresso.
Un'app deve usare CXProvider
per quanto segue:
- Segnalare una chiamata in ingresso al sistema.
- Segnalare una chiamata in uscita connessa al sistema.
- Segnalare all'utente remoto che termina la chiamata al sistema.
Quando l'app vuole comunicare con il sistema, usa la CXCallUpdate
classe e quando il sistema deve comunicare con l'app, usa la CXAction
classe :
The CXCallController
CXCallController
consente a un'app di informare il sistema di azioni dell'utente locale, ad esempio l'utente che avvia una chiamata VOIP. Implementando un'app CXCallController
viene eseguita l'interazione con altri tipi di chiamate nel sistema. Ad esempio, se è già in corso una chiamata telefonica attiva, CXCallController
può consentire all'app VOIP di posizionare tale chiamata in attesa e avviare o rispondere a una chiamata VOIP.
Un'app deve usare CXCallController
per quanto segue:
- Segnalare quando l'utente ha avviato una chiamata in uscita al sistema.
- Segnalare quando l'utente risponde a una chiamata in arrivo al sistema.
- Segnalare quando l'utente termina una chiamata al sistema.
Quando l'app vuole comunicare le azioni dell'utente locale al sistema, usa la CXTransaction
classe :
Implementazione di CallKit
Le sezioni seguenti illustrano come implementare CallKit in un'app VOIP Xamarin.iOS. Per motivi di esempio, questo documento usa il codice dell'app VOIP fittizia MonkeyCall. Il codice presentato qui rappresenta diverse classi di supporto, le parti specifiche di CallKit verranno illustrate in dettaglio nelle sezioni seguenti.
Classe ActiveCall
La ActiveCall
classe viene usata dall'app MonkeyCall per contenere tutte le informazioni su una chiamata VOIP attualmente attiva come indicato di seguito:
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
contiene diverse proprietà che definiscono lo stato della chiamata e due eventi che possono essere generati quando lo stato della chiamata cambia. Poiché si tratta solo di un esempio, esistono tre metodi usati per simulare l'avvio, la risposta e la fine di una chiamata.
Classe StartCallRequest
La StartCallRequest
classe statica fornisce alcuni metodi helper che verranno usati quando si usano le chiamate in uscita:
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;
}
}
}
}
Le CallHandleFromURL
classi e CallHandleFromActivity
vengono usate in AppDelegate per ottenere l'handle di contatto della persona chiamata in una chiamata in uscita. Per altre informazioni, vedere la sezione Gestione delle chiamate in uscita di seguito.
Classe ActiveCallManager
La ActiveCallManager
classe gestisce tutte le chiamate aperte nell'app 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
}
}
Anche in questo caso, poiché si tratta solo di una simulazione, mantiene ActiveCallManager
solo una raccolta di ActiveCall
oggetti e dispone di una routine per trovare una determinata chiamata tramite la relativa UUID
proprietà. Include anche metodi per avviare, terminare e modificare lo stato di attesa di una chiamata in uscita. Per altre informazioni, vedere la sezione Gestione delle chiamate in uscita di seguito.
Classe ProviderDelegate
Come illustrato in precedenza, un fornisce una CXProvider
comunicazione bidirezionale tra l'app e il sistema per le notifiche fuori banda. Lo sviluppatore deve fornire un oggetto personalizzato CXProviderDelegate
e collegarlo a CXProvider
per consentire all'app di gestire gli eventi CallKit fuori banda. MonkeyCall usa quanto segue 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
}
}
Quando viene creata un'istanza di questo delegato, viene passato a ActiveCallManager
che verrà usato per gestire qualsiasi attività di chiamata. Definisce quindi i tipi di handle (CXHandleType
) a cui CXProvider
risponderà:
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
Ottiene l'immagine del modello che verrà applicata all'icona dell'app quando è in corso una chiamata:
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
Questi valori vengono aggregati in un oggetto CXProviderConfiguration
che verrà usato per configurare :CXProvider
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
Il delegato crea quindi un nuovo CXProvider
oggetto con queste configurazioni e si collega a esso:
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
Quando si usa CallKit, l'app non creerà e gestirà più le proprie sessioni audio, ma dovrà configurare e usare una sessione audio che verrà creata e gestita dal sistema.
Se si trattasse di un'app reale, il DidActivateAudioSession
metodo verrà usato per avviare la chiamata con un preconfigurato AVAudioSession
fornito dal sistema:
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
Userebbe anche il DidDeactivateAudioSession
metodo per finalizzare e rilasciare la connessione alla sessione audio fornita dal sistema:
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// releated audio
}
Il resto del codice verrà trattato in dettaglio nelle sezioni seguenti.
Classe AppDelegate
MonkeyCall usa AppDelegate per contenere le istanze di ActiveCallManager
e CXProviderDelegate
che verranno usate in tutta l'app:
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
}
}
I OpenUrl
metodi di override e ContinueUserActivity
vengono usati quando l'app elabora una chiamata in uscita. Per altre informazioni, vedere la sezione Gestione delle chiamate in uscita di seguito.
Gestione delle chiamate in ingresso
Esistono diversi stati e processi che una chiamata VOIP in ingresso può attraversare durante un tipico flusso di lavoro di chiamata in ingresso, ad esempio:
- Informare l'utente (e il sistema) che esiste una chiamata in ingresso.
- Ricezione di una notifica quando l'utente vuole rispondere alla chiamata e inizializzare la chiamata con l'altro utente.
- Informare il sistema e la rete di comunicazione quando l'utente vuole terminare la chiamata corrente.
Le sezioni seguenti illustrano in dettaglio come un'app può usare CallKit per gestire il flusso di lavoro delle chiamate in ingresso, usando di nuovo l'app VOIP MonkeyCall come esempio.
Informare l'utente della chiamata in arrivo
Quando un utente remoto ha avviato una conversazione VOIP con l'utente locale, si verifica quanto segue:
- L'app riceve una notifica dalla rete di comunicazione in cui è presente una chiamata VOIP in ingresso.
- L'app usa per
CXProvider
inviare unCXCallUpdate
oggetto al sistema informandolo della chiamata. - Il sistema pubblica la chiamata all'interfaccia utente di sistema, ai servizi di sistema e a qualsiasi altra app VOIP usando CallKit.
Ad esempio, in 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);
}
});
}
Questo codice crea una nuova CXCallUpdate
istanza e associa un handle a esso che identificherà il chiamante. Successivamente, usa il ReportNewIncomingCall
metodo della CXProvider
classe per informare il sistema della chiamata. Se ha esito positivo, la chiamata viene aggiunta alla raccolta di chiamate attive dell'app, in caso contrario, l'errore deve essere segnalato all'utente.
Utente che risponde alla chiamata in arrivo
Se l'utente vuole rispondere alla chiamata VOIP in ingresso, si verifica quanto segue:
- L'interfaccia utente di sistema informa il sistema che l'utente vuole rispondere alla chiamata VOIP.
- Il sistema invia un oggetto
CXAnswerCallAction
all'appCXProvider
informandolo della finalità di risposta. - L'app informa la rete di comunicazione che l'utente sta rispondendo alla chiamata e la chiamata VOIP procede come di consueto.
Ad esempio, in 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 ();
}
});
}
Questo codice cerca prima di tutto la chiamata specificata nel relativo elenco di chiamate attive. Se la chiamata non viene trovata, il sistema riceve una notifica e il metodo viene chiuso. Se viene trovato, il AnswerCall
metodo della ActiveCall
classe viene chiamato per avviare la chiamata e il sistema è informativo se ha esito positivo o negativo.
Utente che termina la chiamata in arrivo
Se l'utente vuole terminare la chiamata dall'interfaccia utente dell'app, si verifica quanto segue:
- L'app crea
CXEndCallAction
che viene in bundle in un oggettoCXTransaction
inviato al sistema per informarlo che la chiamata sta terminando. - Il sistema verifica la finalità di chiamata finale e invia di
CXEndCallAction
nuovo all'app tramite .CXProvider
- L'app informa quindi la rete di comunicazione che la chiamata sta terminando.
Ad esempio, in 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 ();
}
});
}
Questo codice cerca prima di tutto la chiamata specificata nel relativo elenco di chiamate attive. Se la chiamata non viene trovata, il sistema riceve una notifica e il metodo viene chiuso. Se viene trovato, il EndCall
metodo della classe viene chiamato per terminare la ActiveCall
chiamata e il sistema è informazioni se ha esito positivo o negativo. In caso di esito positivo, la chiamata viene rimossa dalla raccolta di chiamate attive.
Gestione di più chiamate
La maggior parte delle app VOIP può gestire più chiamate contemporaneamente. Ad esempio, se è attualmente presente una chiamata VOIP attiva e l'app riceve una notifica che indica che è presente una nuova chiamata in arrivo, l'utente può sospendere o riattaccarsi alla prima chiamata per rispondere alla seconda.
Nella situazione precedente, il sistema invierà un all'app CXTransaction
che includerà un elenco di più azioni ( ad esempio CXEndCallAction
e ).CXAnswerCallAction
Tutte queste azioni dovranno essere soddisfatte singolarmente, in modo che il sistema possa aggiornare l'interfaccia utente in modo appropriato.
Gestione delle chiamate in uscita
Se l'utente tocca una voce dall'elenco Recenti (nell'app Telefono), ad esempio da una chiamata appartenente all'app, verrà inviata una finalità start call dal sistema:
- L'app creerà un'azione start call in base alla finalità start call ricevuta dal sistema.
- L'app userà per
CXCallController
richiedere l'azione Avvia chiamata dal sistema. - Se il sistema accetta l'azione, verrà restituito all'app tramite il
XCProvider
delegato. - L'app avvia la chiamata in uscita con la relativa rete di comunicazione.
Per altre informazioni sulle finalità, vedere la documentazione sulle finalità e sulle estensioni dell'interfaccia utente delle finalità.
Ciclo di vita delle chiamate in uscita
Quando si usa CallKit e una chiamata in uscita, l'app dovrà informare il sistema degli eventi del ciclo di vita seguenti:
- Avvio : informare il sistema che una chiamata in uscita sta per essere avviata.
- Avviato : informare il sistema che è stata avviata una chiamata in uscita.
- Connessione: informare il sistema che la chiamata in uscita si sta connettendo.
- Connessione ed - Informare che la chiamata in uscita è connessa e che entrambe le parti possono parlare ora.
Ad esempio, il codice seguente avvierà una chiamata in uscita:
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);
}
Crea un CXHandle
oggetto e lo usa per configurare un CXStartCallAction
oggetto che viene inserito in un CXTransaction
oggetto inviato al sistema usando il RequestTransaction
metodo della CXCallController
classe . Chiamando il RequestTransaction
metodo, il sistema può effettuare qualsiasi chiamata esistente in attesa, indipendentemente dall'origine (Telefono'app, FaceTime, VOIP e così via), prima dell'avvio della nuova chiamata.
La richiesta di avviare una chiamata VOIP in uscita può provenire da diverse origini, ad esempio Siri, una voce in una scheda contatto (nell'app Contatti) o dall'elenco Recenti (nell'app Telefono). In queste situazioni, l'app verrà inviata una finalità di chiamata start all'interno di un NSUserActivity
e AppDelegate dovrà gestirla:
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;
}
}
In questo caso viene usato il CallHandleFromActivity
metodo della classe StartCallRequest
helper per ottenere l'handle alla persona chiamata (vedere la classe StartCallRequest precedente).
Il PerformStartCallAction
metodo della classe ProviderDelegate viene usato per avviare infine la chiamata in uscita effettiva e informare il sistema del ciclo di vita:
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 ();
}
});
}
Crea un'istanza della ActiveCall
classe (per contenere informazioni sulla chiamata in corso) e popola con la persona chiamata. Gli StartingConnectionChanged
eventi e ConnectedChanged
vengono usati per monitorare e segnalare il ciclo di vita delle chiamate in uscita. La chiamata viene avviata e il sistema ha informato che l'azione è stata soddisfatta.
Fine di una chiamata in uscita
Quando l'utente ha terminato una chiamata in uscita e vuole terminarla, è possibile usare il codice seguente:
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);
}
Se crea un CXEndCallAction
oggetto con l'UUID della chiamata alla fine, lo aggrega in un oggetto CXTransaction
inviato al sistema usando il RequestTransaction
metodo della CXCallController
classe .
Dettagli aggiuntivi sul CallKit
Questa sezione illustra alcuni dettagli aggiuntivi che lo sviluppatore dovrà prendere in considerazione quando si usa CallKit, ad esempio:
- Configurazione del provider
- Errori delle azioni
- Restrizioni di sistema
- VOIP Audio
Configurazione dei provider
La configurazione del provider consente a un'app VOIP iOS 10 di personalizzare l'esperienza utente (all'interno dell'interfaccia utente in chiamata nativa) quando si usa CallKit.
Un'app può effettuare i tipi di personalizzazioni seguenti:
- Visualizzare un nome localizzato.
- Abilitare il supporto di videochiamata.
- Personalizzare i pulsanti nell'interfaccia utente in chiamata presentando la propria icona dell'immagine modello. L'interazione dell'utente con pulsanti personalizzati viene inviata direttamente all'app da elaborare.
Errori di azione
Le app VOIP iOS 10 che usano CallKit devono gestire le azioni con esito negativo e mantenere sempre informato l'utente dello stato azione.
Prendere in considerazione l'esempio seguente:
- L'app ha ricevuto un'azione di chiamata di avvio e ha iniziato il processo di inizializzazione di una nuova chiamata VOIP con la rete di comunicazione.
- A causa di funzionalità di comunicazione di rete limitate o non disponibili, la connessione non riesce.
- L'app deve inviare il messaggio Failback all'azione Avvia chiamata (
Action.Fail()
) per informare il sistema dell'errore. - Ciò consente al sistema di informare l'utente dello stato della chiamata. Ad esempio, per visualizzare l'interfaccia utente dell'errore di chiamata.
Inoltre, un'app VOIP iOS 10 dovrà rispondere agli errori di timeout che possono verificarsi quando un'azione prevista non può essere elaborata entro un determinato periodo di tempo. A ogni tipo di azione fornito da CallKit è associato un valore di timeout massimo. Questi valori di timeout assicurano che qualsiasi azione CallKit richiesta dall'utente venga gestita in modo reattivo, mantenendo il sistema operativo fluido e reattivo.
Esistono diversi metodi nel delegato del provider (CXProviderDelegate
) che devono essere sottoposti a override per gestire correttamente anche queste situazioni di timeout.
Restrizioni di sistema
In base allo stato corrente del dispositivo iOS che esegue l'app iOS 10 VOIP, è possibile applicare determinate restrizioni di sistema.
Ad esempio, una chiamata VOIP in ingresso può essere limitata dal sistema se:
- La persona che chiama è nell'elenco dei chiamanti bloccati dell'utente.
- Il dispositivo iOS dell'utente è in modalità Non disturbare.
Se una chiamata VOIP è limitata da una di queste situazioni, usare il codice seguente per gestirla:
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 diversi vantaggi per la gestione delle risorse audio necessarie per un'app VOIP iOS 10 durante una chiamata VOIP in tempo reale. Uno dei principali vantaggi è che la sessione audio dell'app avrà priorità elevate durante l'esecuzione in iOS 10. Questo è lo stesso livello di priorità delle app predefinite Telefono e FaceTime e questo livello di priorità avanzata impedirà ad altre app in esecuzione di interrompere la sessione audio dell'app VOIP.
Inoltre, CallKit ha accesso ad altri hint di routing audio che possono migliorare le prestazioni e instradare in modo intelligente l'audio VOIP a dispositivi di output specifici durante una chiamata live in base alle preferenze utente e agli stati del dispositivo. Ad esempio, in base a dispositivi collegati, ad esempio cuffie Bluetooth, una connessione CarPlay live o impostazioni di accessibilità.
Durante il ciclo di vita di una tipica chiamata VOIP tramite CallKit, l'app dovrà configurare il flusso audio che callkit fornirà. Esaminare l'esempio seguente:
- Un'azione di chiamata di avvio viene ricevuta dall'app per rispondere a una chiamata in arrivo.
- Prima che questa azione venga soddisfatta dall'app, fornisce la configurazione necessaria per il relativo
AVAudioSession
. - L'app informa il sistema che l'azione è stata soddisfatta.
- Prima che la chiamata si connetta, CallKit fornisce una priorità
AVAudioSession
elevata corrispondente alla configurazione richiesta dall'app. L'app riceverà una notifica tramite ilDidActivateAudioSession
metodo del relativoCXProviderDelegate
oggetto .
Uso delle estensioni della directory delle chiamate
Quando si usa CallKit, le estensioni della directory di chiamata consentono di aggiungere numeri di chiamata bloccati e identificare i numeri specifici di una determinata app VOIP ai contatti nell'app Contatto nel dispositivo iOS.
Implementazione di un'estensione della directory di chiamata
Per implementare un'estensione directory di chiamata in un'app Xamarin.iOS, eseguire le operazioni seguenti:
Aprire la soluzione dell'app in Visual Studio per Mac.
Fare clic con il pulsante destro del mouse sul nome della soluzione nel Esplora soluzioni e scegliere Aggiungi>nuovo progetto.
Selezionare Estensioni iOS>Chiama estensioni> directory e fare clic sul pulsante Avanti:
Immettere un nome per l'estensione e fare clic sul pulsante Avanti :
Modificare il nome del progetto e/o il nome della soluzione, se necessario, e fare clic sul pulsante Crea:
Verrà aggiunta una CallDirectoryHandler.cs
classe al progetto simile alla seguente:
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
}
}
Il BeginRequest
metodo nel gestore della directory di chiamata dovrà essere modificato per fornire la funzionalità necessaria. Nel caso dell'esempio precedente, tenta di impostare l'elenco di numeri bloccati e disponibili nel database dei contatti dell'app VOIP. Se una delle richieste ha esito negativo per qualsiasi motivo, creare un oggetto NSError
per descrivere l'errore e passarlo al CancelRequest
metodo della CXCallDirectoryExtensionContext
classe .
Per impostare i numeri bloccati, utilizzare il AddBlockingEntry
metodo della CXCallDirectoryExtensionContext
classe . I numeri forniti al metodo devono essere in ordine numerico crescente. Per prestazioni ottimali e utilizzo della memoria quando sono presenti molti numeri di telefono, è consigliabile caricare solo un sottoinsieme di numeri in un determinato momento e usare pool di versioni automatica per rilasciare gli oggetti allocati durante ogni batch di numeri caricati.
Per informare l'app Contatto dei numeri di contatto noti all'app VOIP, usare il AddIdentificationEntry
metodo della CXCallDirectoryExtensionContext
classe e specificare sia il numero che un'etichetta di identificazione. Anche in questo caso, i numeri forniti al metodo devono essere in ordine numerico crescente. Per prestazioni ottimali e utilizzo della memoria quando sono presenti molti numeri di telefono, è consigliabile caricare solo un sottoinsieme di numeri in un determinato momento e usare pool di versioni automatica per rilasciare gli oggetti allocati durante ogni batch di numeri caricati.
Riepilogo
Questo articolo ha illustrato la nuova API CallKit rilasciata da Apple in iOS 10 e come implementarla nelle app VOIP Xamarin.iOS. Ha mostrato in che modo CallKit consente a un'app di integrarsi nel sistema iOS, come fornisce parità di funzionalità con le app predefinite (ad esempio Telefono) e come aumenta la visibilità di un'app in tutti iOS in posizioni come blocco e schermate home, tramite interazioni siri e tramite le app Contatti.