CallKit v Xamarin.iOS
Nové rozhraní CallKit API v iOSu 10 poskytuje aplikacím VOIP možnost integrace s uživatelským rozhraním iPhone a známé rozhraní a prostředí pro koncového uživatele. Pomocí tohoto rozhraní API mohou uživatelé zobrazit a interagovat s voláními VOIP ze zamykací obrazovky zařízení s iOSem a spravovat kontakty pomocí zobrazení Oblíbené a Poslední položky aplikace Telefon.
Informace o CallKitu
CallKit je podle společnosti Apple novou architekturou, která povýší aplikace Voice Over IP (VOIP) třetích stran na prostředí první strany v iOSu 10. Rozhraní API CallKit umožňuje aplikacím VOIP integraci s uživatelským rozhraním iPhone a poskytuje známé rozhraní a prostředí pro koncového uživatele. Stejně jako integrovaná aplikace Telefon může uživatel zobrazit a pracovat s voláními VOIP ze zamykací obrazovky zařízení s iO Telefon OS a spravovat kontakty pomocí zobrazení Oblíbené a Poslední.
Kromě toho rozhraní Api CallKit poskytuje možnost vytvářet rozšíření aplikací, která mohou přidružit telefonní číslo k jménu (ID volajícího) nebo systému říct, kdy má být číslo blokované (blokování volání).
Stávající prostředí aplikace VOIP
Než začnete probírat nové rozhraní CallKit API a jeho schopnosti, podívejte se na aktuální uživatelské prostředí s aplikací VOIP třetí strany v iOSu 9 (a méně) pomocí fiktivní aplikace VOIP s názvem CallCall. CallCall je jednoduchá aplikace, která umožňuje uživateli odesílat a přijímat volání VOIP pomocí stávajících rozhraní API pro iOS.
V současné době, pokud uživatel přijímá příchozí volání na Call a jeho iPhone je uzamčen, oznámení přijaté na zamykací obrazovce je nerozlišitelné od jakéhokoli jiného typu oznámení (například z aplikací Zprávy nebo Pošta).
Pokud by uživatel chtěl na volání odpovědět, muset vysunout oznámení Call, aby otevřel aplikaci a zadat heslo (nebo uživatelské Touch ID), aby telefon odemkl, než by mohl přijmout hovor a zahájit konverzaci.
Pokud je telefon odemknutý, je prostředí stejně těžkopádné. Opět se příchozí voláníCall upozorní jako standardní informační zpráva, která se vysunout z horní části obrazovky. Vzhledem k tomu, že je oznámení dočasné, může ho uživatel snadno vynechat tím, že je vynutí otevřít centrum oznámení a najít konkrétní oznámení, na které má odpovědět, a potom zavolat nebo vyhledat a spustit aplikaciCall ručně.
Prostředí aplikace CallKit VOIP
Implementací nových rozhraní Api CallKitu v aplikaci CallCall lze v iOSu 10 výrazně zlepšit uživatelské prostředí s příchozím voláním VOIP. Podívejte se na příklad uživatele, který přijímá volání VOIP, když je jeho telefon zamčený výše. Implementací CallKitu se volání zobrazí na zamykací obrazovce iPhone stejně jako při přijetí volání z integrované aplikace Telefon s nativním uživatelským rozhraním a standardní funkcí potažením prstem na celou obrazovku.
Opět platí, že pokud je iPhone odemknut při přijetí volání VOIP NazíkaCall, zobrazí se stejné nativní uživatelské rozhraní a standardní funkce potažením prstem a klepnutím na odmítnutím integrované aplikace Telefon a Má Možnost přehrát si vlastní náplň.
CallKit poskytuje další funkce pro AjaxCall, což umožňuje volání VOIP pro interakci s jinými typy volání, aby se objevily v předdefinových seznamech Recents a Favorite, aby bylo možné používat integrované funkce Do Not Disturb a Block, začítá volání Typu volejte ze Siri a nabízí uživatelům možnost přiřazovat volání AjaxCall lidem v aplikaci Kontakty.
Následující části podrobně popisují architekturu CallKitu, příchozí a odchozí toky volání a rozhraní CallKit API.
Architektura CallKitu
V iOSu 10 společnost Apple přijala CallKit ve všech systémových službách tak, aby volání provedená v CarPlay byla například známa systémového uživatelského rozhraní prostřednictvím CallKitu. V příkladu níže platí, že vzhledem k tomu, že Sekažka přijme CallKit, je systém známý stejným způsobem jako tyto předdefinované systémové služby a získá všechny stejné funkce:
Z výše uvedeného diagramu se podívejte na aplikaciPickaCall. Aplikace obsahuje veškerý svůj kód pro komunikaci s vlastní sítí a obsahuje vlastní uživatelská rozhraní. Odkazuje v CallKitu na komunikaci se systémem:
V CallKitu existují dvě hlavní rozhraní, která aplikace používá:
CXProvider– To umožňuje aplikaciCall, aby systém informovala o všech oznámeních mimo pásmo, ke kterým může dojít.CXCallController– Povolí aplikaciCall informovat systém o akcích místního uživatele.
Zprostředkovatel CX
Jak je uvedeno výše, umožňuje aplikaci informovat systém o všech oznámeních mimo pásmo, ke kterým CXProvider může dojít. Jedná se o oznámení, ke kterým nedochází kvůli akcím místního uživatele, ale dochází k nim kvůli externím událostem, jako jsou příchozí volání.
Aplikace by měla použít CXProvider pro následující:
- Nahlásit příchozí volání do systému.
- Nahlásit, že odchozí volání se připojilo k systému.
- Nahlásit vzdálenému uživateli ukončení volání do systému.
Když chce aplikace komunikovat se systémem, používá třídu a když systém potřebuje komunikovat s CXCallUpdate aplikací, používá CXAction třídu :
The CXCallController
Umožňuje aplikaci informovat systém o akcích místního uživatele, jako je například spuštění volání CXCallController VOIP. Implementací aplikace CXCallController se aplikace přehraje s jinými typy volání v systému. Pokud už například probíhá aktivní telefonní hovor, může aplikace VOIP povolit, aby toto volání přidrží a svolala nebo odpověděla na CXCallController volání VOIP.
Aplikace by měla použít CXCallController pro následující:
- Nahlásit, když uživatel zahájil odchozí volání do systému.
- Nahlásit, když uživatel odpoví na příchozí volání do systému.
- Nahlásit, když uživatel ukončí volání do systému.
Když chce aplikace sdělit systému místní akce uživatele, používá CXTransaction třídu :
Implementace CallKitu
Následující části vám ukážou, jak implementovat CallKit v aplikaci VOIP xamarin.iOS. Kvůli příkladu bude tento dokument používat kód z fiktivní aplikace VOIP SébaCall. Zde prezentované kódy představují několik podpůrných tříd. Konkrétní části CallKitu jsou podrobně popsány v následujících částech.
Třída ActiveCall
Třída je používána aplikací XboxCall k tomu, aby uchová všechny informace o volání ActiveCall VOIP, které je aktuálně aktivní, následujícím způsobem:
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 obsahuje několik vlastností, které definují stav volání, a dvě události, které mohou být vyvolány při změně stavu volání. Vzhledem k tomu, že se jedná pouze o příklad, existují tři metody, které slouží k simulaci spuštění, zodpovězení a ukončení volání.
Třída StartCallRequest
Statická třída poskytuje několik pomocných metod, které se budou používat StartCallRequest při práci s odchozími voláními:
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;
}
}
}
}
Třídy a se používají v AppDelegate k získání popisovače kontaktu osoby, která je volána CallHandleFromURLCallHandleFromActivity v odchozím hovoru. Další informace najdete níže v části Zpracování odchozích volání.
Třída ActiveCallManager
Třída ActiveCallManager zpracovává všechna otevřená volání v aplikaciCall.
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
}
}
Vzhledem k tomu, že se jedná pouze o simulaci, jediná udržuje kolekci objektů a má rutinu pro vyhledání daného volání ActiveCallManagerActiveCall jeho UUID vlastností. Zahrnuje také metody pro spuštění, ukončení a změnu stavu přidržení odchozího volání. Další informace najdete níže v části Zpracování odchozích volání.
Třída ProviderDelegate
Jak je uvedeno výše, poskytuje dvousměrná komunikace mezi aplikací a systémem pro oznámení mimo CXProvider pásmo. Vývojář musí poskytnout vlastní a připojit ho k , aby aplikace zřela události CXProviderDelegateCXProvider CallKitu mimo pásmo. Při použití funkce UceCall se používá CXProviderDelegate následující:
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
}
}
Při vytvoření instance tohoto delegáta se předá , který bude používat ke zpracování ActiveCallManager jakékoli aktivity volání. Dále definuje typy popisovačů ( CXHandleType ), na které bude CXProvider reagovat :
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
A získá obrázek šablony, který se použije na ikonu aplikace, když probíhá volání:
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
Tyto hodnoty se zabalí do CXProviderConfiguration objektu , který se použije ke konfiguraci CXProvider :
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
Delegát pak vytvoří nový s těmito konfiguracemi a připojí CXProvider se k ní:
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
Při použití CallKitu už aplikace nebude vytvářet a zpracovávat vlastní zvukové relace, místo toho bude muset nakonfigurovat a používat zvukové relace, které systém vytvoří a zřídí pro něj.
Pokud by se jedná o skutečnou aplikaci, použila by se metoda ke spuštění volání s předem nakonfigurovanou DidActivateAudioSessionAVAudioSession možností, kterou systém poskytl:
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
K dokončení a uvolnění připojení ke zvukové relaci poskytované systémem by také mohla použít DidDeactivateAudioSession metodu :
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// releated audio
}
Zbývající část kódu bude podrobně popsána v následujících částech.
Třída AppDelegate
K blokování instancí objektu a , které se budou používat v celé aplikaci, používá Metoda UchytiCall ActiveCallManager funkci AppDelegate: CXProviderDelegate
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
}
}
Metody OpenUrlContinueUserActivity přepsání a se používají, když aplikace zpracovává odchozí volání. Další informace najdete níže v části Zpracování odchozích volání.
Zpracování příchozích volání
Existuje několik stavů a procesů, které příchozí volání VOIP může projít během typického pracovního postupu příchozího volání, například:
- Informing the user (and the System) that an incoming call exists.
- Přijetí oznámení, když uživatel chce odpovědět na volání a inicializaci volání s jiným uživatelem.
- Informujte systém a komunikační síť, když chce uživatel ukončit aktuální volání.
V následujících částech se podrobně podíváme na to, jak může aplikace používat CallKit ke zpracování pracovního postupu příchozího volání, a znovu jako příklad použije aplikaci VOIP s voláním Nazmimi.
Informování uživatele o příchozím volání
Když vzdálený uživatel zahájil konverzaci VOIP s místním uživatelem, dojde k následujícímu:
- Aplikace dostane ze své komunikační sítě oznámení, že došlo k příchozímu volání VOIP.
- Aplikace používá k
CXProviderodeslání doCXCallUpdatesystému informující o volání. - Systém publikuje volání do uživatelského rozhraní systému, systémových služeb a dalších aplikací VOIP pomocí CallKit.
Například v 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);
}
});
}
Tento kód vytvoří novou CXCallUpdate instanci a připojí popisovač k tomuto objektu, který identifikuje volajícího. Dále používá ReportNewIncomingCall metodu CXProvider třídy k informování systému o volání. Je-li to úspěšné, volání je přidáno do kolekce aktivních volání aplikace, pokud ne, je nutné chybu oznámit uživateli.
Příchozí volání uživatele do zodpovězení
Pokud uživatel chce odpovědět na příchozí volání VOIP, dojde k následujícímu:
- Systémové uživatelské rozhraní informuje systém o tom, že uživatel chce odpovědět na volání VOIP.
- Systém pošle
CXAnswerCallActionaplikaci, která bude informovat oCXProviderzáměru odpovědi. - Aplikace informuje svou komunikační síť, kterou uživatel odpovídá na volání, a volání VOIP pokračuje obvyklým způsobem.
Například v 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 ();
}
});
}
Tento kód nejprve vyhledá dané volání v seznamu aktivních volání. Pokud se volání nenajde, systém se oznámí a metoda se ukončí. Pokud je nalezen, AnswerCall Metoda ActiveCall třídy je volána pro spuštění volání a systém je informace v případě úspěchu nebo neúspěchu.
Uživatel ukončuje příchozí volání.
Pokud si uživatel přeje ukončit volání v uživatelském rozhraní aplikace, dojde k následujícímu:
- Aplikace vytvoří balíček
CXEndCallAction, který je součástí nástrojeCXTransaction, který je odeslán do systému za účelem informování o tom, že volání končí. - Systém ověří záměr koncového volání a pošle
CXEndCallActionzpátky do aplikace prostřednictvímCXProvider. - Aplikace poté informuje svou komunikační síť o ukončení volání.
Například v 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 ();
}
});
}
Tento kód nejprve vyhledá dané volání v seznamu aktivních volání. Pokud se volání nenajde, systém se oznámí a metoda se ukončí. Pokud je nalezen, EndCall Metoda ActiveCall třídy je volána pro ukončení volání a systém je informace, pokud je úspěch nebo selže. Je-li to úspěšné, volání je odebráno z kolekce aktivních volání.
Správa více volání
Většina aplikací VOIP může zpracovávat více volání najednou. Pokud je například aktuálně aktivní volání VOIP a aplikace dostane oznámení o novém příchozím volání, může uživatel pozastavit nebo zablokovat první volání a odpovědět druhému.
V takovém případě se systém pošle CXTransaction do aplikace, která bude obsahovat seznam více akcí (například CXEndCallAction a CXAnswerCallAction ). Všechny tyto akce bude nutné splnit jednotlivě, aby systém mohl odpovídajícím způsobem aktualizovat uživatelské rozhraní.
Zpracování odchozích volání
pokud uživatel klepne na položku ze seznamu posledních položek (v aplikaci Telefon), například z volání patřícího do aplikace, pošle se do systému spouštěcí záměr volání :
- Aplikace vytvoří akci spustit volání v závislosti na počátečním záměru volání, který přijal ze systému.
- Aplikace bude používat
CXCallControllerk žádosti o akci spustit hovor ze systému. - Pokud systém přijme akci, bude vrácena do aplikace prostřednictvím
XCProviderdelegáta. - Aplikace spustí odchozí volání v rámci komunikační sítě.
Další informace o záměrech najdete v dokumentaci k našim záměrům a rozšířením uživatelského rozhraní .
Životní cyklus odchozího volání
Když pracujete s CallKit a odchozím voláním, aplikace bude muset informovat systém o následujících událostech životního cyklu:
- Od začátku – informujte systém, že se chystá spuštění odchozího volání.
- Začalo – informujte systém, zda bylo zahájeno odchozí volání.
- Připojování – informujte systém, že odchozí volání se připojuje.
- Připojeno – informujte, že odchozí volání se připojilo a že obě strany si můžou mluvit hned.
Například následující kód spustí odchozí volání:
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);
}
Vytvoří CXHandle a použije ho ke konfiguraci CXStartCallAction , která je součástí balíčku CXTransaction , který je odeslán do systému pomocí RequestTransaction metody CXCallController třídy. když zavoláte RequestTransaction metodu, systém může umístit jakákoli existující volání za běhu, bez ohledu na zdroj (Telefon app, FaceTime, VOIP atd.) před zahájením nového volání.
požadavek na spuštění odchozího volání VOIP může pocházet z několika různých zdrojů, například Siri, položky na kartě kontaktu (v aplikaci kontaktů) nebo ze seznamu posledních položek (v aplikaci Telefon). V takových situacích se aplikace pošle záměr zahájit volání uvnitř NSUserActivity a a AppDelegate ho bude muset zpracovat:
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;
}
}
Zde CallHandleFromActivity je metoda pomocné třídy StartCallRequest používána k získání popisovače na volanou osobu (viz CallHandleFromActivity výše).
PerformStartCallActionMetoda PerformStartCallAction slouží ke konečnému spuštění samotného odchozího hovoru a informování o jeho životním cyklu:
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 ();
}
});
}
Vytvoří instanci ActiveCall třídy (pro uchovávání informací o probíhajícím volání) a naplní ji volaným uživatelem. StartingConnectionChangedUdálosti a ConnectedChanged se používají k monitorování a hlášení životního cyklu odchozího volání. Volání je zahájeno a systém informoval, že akce byla splněna.
Ukončení odchozího volání
Až se uživatel dokončí odchozím voláním a chce ho ukončit, můžete použít následující kód:
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);
}
Je-li vytvořena CXEndCallAction s identifikátorem UUID volání metody end, sada vytvoří balíček v CXTransaction , který je odeslán do systému pomocí RequestTransaction metody CXCallController třídy.
Další podrobnosti CallKit
Tato část se zabývá dalšími podrobnostmi, které vývojář bude muset vzít v úvahu při práci s CallKit, jako je:
- Konfigurace zprostředkovatele
- Chyby akce
- Systémová omezení
- Zvuk VOIP
Konfigurace zprostředkovatele
Konfigurace zprostředkovatele umožňuje aplikaci VOIP s iOS 10 přizpůsobit uživatelské prostředí (uvnitř nativního In-Call uživatelského rozhraní) při práci s CallKit.
Aplikace může provádět následující typy přizpůsobení:
- Zobrazit lokalizovaný název.
- Povolte podporu pro audiovizuální volání.
- Přizpůsobte tlačítka v uživatelském rozhraní In-Call tím, že prezentujete vlastní ikonu obrázku šablony. Interakce uživatele s vlastními tlačítky se odesílá přímo do aplikace ke zpracování.
Chyby akce
aplikace VOIP s iOS 10, které používají CallKit, potřebují k řádnému zpracování akcí a zajištění, aby uživatel včas informoval o stavu akce.
Vezměte v úvahu následující příklad:
- Aplikace přijala akci spustit hovor a začala Postup inicializace nového volání VOIP v rámci komunikační sítě.
- Kvůli omezené nebo žádné síťové komunikační možnosti se připojení nezdařilo.
- Aplikace musí odeslat zprávu o selhání zpět do akce zahájit volání ( ), aby informovala systém o selhání.
- To umožňuje systému informovat uživatele o stavu volání. Například pro zobrazení uživatelského rozhraní selhání volání.
Kromě toho bude muset aplikace VOIP s iOS 10 reagovat na chyby časového limitu , ke kterým může dojít v případě, že se Očekávaná akce nedá zpracovat v daném časovém intervalu. Každý typ akce poskytovaný CallKit má přiřazenou maximální hodnotu časového limitu. Tyto hodnoty časového limitu zajišťují, že všechny akce CallKit vyžadované uživatelem jsou zpracovávány způsobem reakce, čímž se zachovává i kapalina operačního systému a také reakce.
U delegáta poskytovatele () existuje několik metod CXProviderDelegate , které by měly být přepsány, aby se mohl řádně zvládnout i tato situace s časovým limitem.
Systémová omezení
Na základě aktuálního stavu zařízení s iOS, na kterém běží aplikace VOIP pro iOS 10, se můžou vymáhat určitá systémová omezení.
Například příchozí volání VOIP může být omezeno systémem, pokud:
- Volající uživatel se nachází v seznamu blokovaných volajících uživatele.
- Zařízení iOS uživatele je v režimu Nerušit.
Pokud je volání VOIP omezeno některou z těchto situací, použijte následující kód k jeho zpracování:
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
}
}
});
}
}
Zvuk VOIP
CallKit poskytuje několik výhod pro zpracování zvukových prostředků, které bude aplikace VOIP pro iOS 10 vyžadovat během živého volání VOIP. Jednou z největších výhod je, že zvuková relace aplikace bude mít vyšší prioritu při provozu v iOS 10. jedná se o stejnou úroveň priority, jakou mají předdefinované aplikace Telefon a FaceTime, a tato rozšířená úroveň priority zabrání ostatním běžícím aplikacím v přerušování zvukové relace aplikace VOIP.
CallKit má navíc přístup k dalším doporučením pro směrování zvuku, která můžou zlepšit výkon a inteligentní směrování zvuku VOIP na konkrétní výstupní zařízení během živého volání na základě uživatelských preferencí a stavů zařízení. Například na základě připojených zařízení, jako jsou například sluchátka Bluetooth, připojení živého CarPlay nebo nastavení usnadnění.
Během životního cyklu typického volání VOIP pomocí CallKit bude aplikace muset nakonfigurovat zvukový stream, který CallKit poskytne. Podívejte se na následující příklad:
- Aplikace přijme akci spustit volání, aby odpovídala příchozímu volání.
- Než tuto akci splní aplikace, poskytne konfiguraci, která bude pro svůj požadavek vyžadovat
AVAudioSession. - Aplikace informuje systém o tom, že akce byla splněna.
- Před připojením volání CallKit poskytuje vysokou prioritu
AVAudioSessionodpovídající konfiguraci, kterou aplikace požaduje. Aplikace bude oznámena prostřednictvímDidActivateAudioSessionmetodyCXProviderDelegate.
Práce s rozšířeními adresáře volání
Při práci s CallKit poskytují rozšíření adresáře volání způsob, jak přidat blokovaná čísla volání a identifikovat čísla, která jsou specifická pro danou aplikaci VoIP, do kontaktů v aplikaci kontaktu na zařízení s iOS.
Implementace rozšíření adresáře volání
Pokud chcete implementovat rozšíření adresáře volání v aplikaci Xamarin. iOS, udělejte toto:
otevřete řešení aplikace v Visual Studio pro Mac.
klikněte pravým tlačítkem myši na název řešení v Průzkumník řešení a vyberte přidatpřidání nové Project.
Vyberte rozšíření pro iOSrozšířeníadresáře volání a klikněte na tlačítko Další :
Zadejte název rozšíření a klikněte na tlačítko Další :
v případě potřeby upravte název Project nebo název řešení a klikněte na tlačítko vytvořit :
Tím se CallDirectoryHandler.cs do projektu přidá třída, která vypadá následovně:
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
}
}
BeginRequestMetodu v obslužné rutině adresáře volání bude nutné upravit, aby poskytovala požadovanou funkci. V případě výše uvedeného příkladu se pokusí nastavit seznam blokovaných a dostupných čísel v databázi kontaktů aplikace VOIP. Pokud z nějakého důvodu selže některá z těchto požadavků, vytvořte NSError pro popis selhání a předejte ho CancelRequest metodě CXCallDirectoryExtensionContext třídy.
Pro nastavení blokovaných čísel použijte AddBlockingEntry metodu CXCallDirectoryExtensionContext třídy. Čísla poskytnutá metodě musí být v číselném vzestupném pořadí. Optimální využití výkonu a paměti v případě, že existuje mnoho telefonních čísel, zvažte pouze načtení podmnožiny čísel v daném čase a používání fondů pro vydaných verzí k uvolnění objektů přidělených během každé dávky čísel, která jsou načtena.
Chcete-li se informovat o kontaktování aplikace s kontaktními čísly, které zná aplikace VOIP, použijte AddIdentificationEntry metodu CXCallDirectoryExtensionContext třídy a zadejte jak číslo, tak identifikační popisek. Čísla poskytnutá metodě musí být v číselném vzestupném pořadí. Optimální využití výkonu a paměti v případě, že existuje mnoho telefonních čísel, zvažte pouze načtení podmnožiny čísel v daném čase a používání fondů pro vydaných verzí k uvolnění objektů přidělených během každé dávky čísel, která jsou načtena.
Souhrn
Tento článek pojednává o novém rozhraní CallKit API, které Apple vydalo v iOS 10 a jak ho implementovat v aplikacích Xamarin. iOS VOIP. ukazují, jak CallKit umožňuje integraci aplikace do systému iOS, jak poskytuje paritu funkcí pomocí integrovaných aplikací (například Telefon) a jak zvyšuje viditelnost aplikace v rámci iOS v místech, jako jsou zámky a domovské obrazovky, prostřednictvím Siri interakcí a aplikací kontaktů.












