CallKit v Xamarin.iOS
Nové rozhraní CallKit API v iOSu 10 poskytuje způsob, jak integrovat aplikace VOIP s uživatelským rozhraním i Telefon a poskytnout koncovému uživateli známé rozhraní a prostředí. Díky tomuto rozhraní API můžou uživatelé rozhraní API zobrazovat a pracovat 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
Podle Společnosti Apple je CallKit novou architekturou, která zvýší úroveň aplikací Voice Over IP (VOIP) třetích stran na prostředí 1. strany v iOSu 10. Rozhraní CallKit API umožňuje, aby se aplikace VOIP integrují s uživatelským rozhraním i Telefon a koncovým uživatelům poskytovaly známé rozhraní a prostředí. Stejně jako integrovaná aplikace Telefon může uživatel zobrazovat a pracovat 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.
Kromě toho rozhraní CallKit API poskytuje možnost vytvářet rozšíření aplikací, která můžou přidružit telefonní číslo ke jménu (ID volajícího) nebo říct systému, kdy má být číslo zablokováno (blokování volání).
Stávající prostředí aplikace VOIP
Než probereme 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 menší) pomocí fiktivní aplikace VOIP s názvem MonkeyCall. MonkeyCall je jednoduchá aplikace, která uživateli umožňuje 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 dostává příchozí hovor na MonkeyCall a jeho i Telefon 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 chce uživatel hovor přijmout, bude muset posunout oznámení MonkeyCall, aby otevřel aplikaci a zadal heslo (nebo touch ID uživatele), aby telefon odemkl, než mohl hovor přijmout a zahájit konverzaci.
Prostředí je stejně těžkopádné, pokud je telefon odemknutý. Příchozí hovor MonkeyCall se znovu zobrazí jako standardní informační nápis, který se posune z horní části obrazovky. Vzhledem k tomu, že oznámení je dočasné, může ho uživatel snadno zmeškat, aby buď otevřel Centrum oznámení, a našel konkrétní oznámení, aby pak volal nebo našel a spustil aplikaci MonkeyCall ručně.
Prostředí aplikace CallKit VOIP
Díky implementaci nových rozhraní API CallKitu v aplikaci MonkeyCall může být prostředí uživatele s příchozím voláním VOIP výrazně vylepšeno v iOSu 10. Příklad uživatele, který přijímá hovor VOIP, když je telefon uzamčen od výše. Implementací CallKitu se volání zobrazí na zamykací obrazovce i Telefon stejně jako kdyby se hovor přijímal z integrované aplikace Telefon s nativním uživatelským rozhraním a standardní funkcí potáhnutí prstem na odpověď.
Opět platí, že pokud i Telefon je odemknutý při přijetí volání MonkeyCall VOIP, stejné celé obrazovky, nativní uživatelské rozhraní a standardní funkce potáhnutí prstem na odpověď a klepnutí na odmítnutí integrované aplikace Telefon je prezentována a MonkeyCall má možnost přehrávat vlastní vyzváněcí tón.
CallKit poskytuje pro MonkeyCall další funkce, které umožňují volání VOIP komunikovat s jinými typy volání, aby se objevila v předdefinovaných seznamech Poslední a Oblíbené, aby používala integrované funkce Nerušit a Blokovat, spusťte volání MonkeyCall ze Siri a nabízí uživatelům možnost přiřazovat volání MonkeyCall lidem v aplikaci Kontakty.
V následujících částech najdete podrobnosti o architektuře CallKitu, příchozích a odchozích voláních a rozhraní API CallKitu.
Architektura CallKitu
V iOSu 10 společnost Apple přijala CallKit ve všech systémových službách tak, aby volání na CarPlay, například, jsou známé v uživatelském rozhraní systému prostřednictvím CallKit. V následujícím příkladu, protože MonkeyCall přijímá CallKit, je známý systémem stejným způsobem jako tyto integrované systémové služby a získá všechny stejné funkce:
Podívejte se blíže na aplikaci MonkeyCall z výše uvedeného diagramu. 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 aplikaci MonkeyCall informovat systém o všech nesmítěných oznámeních, ke kterým může dojít.CXCallController
- Umožňuje aplikaci MonkeyCall informovat systém místních uživatelských akcí.
The CXProvider
Jak je uvedeno výše, CXProvider
umožňuje aplikaci informovat systém o případných nesrozuměných oznámeních. Jedná se o oznámení, ke kterým nedochází kvůli místním uživatelským akcím, ale dochází k nim kvůli externím událostem, jako jsou příchozí hovory.
Aplikace by měla používat CXProvider
následující:
- Nahlašte příchozí hovor do systému.
- Nahlašte, že odchozí hovor je připojený k systému.
- Nahlašte vzdáleného uživatele, který ukončí volání systému.
Když chce aplikace komunikovat se systémem, používá CXCallUpdate
třídu a když systém potřebuje komunikovat s aplikací, používá třídu CXAction
:
The CXCallController
Umožňuje CXCallController
aplikaci informovat systém o akcích místního uživatele, jako je například uživatel, který spouští volání VOIP. Implementace CXCallController
aplikace se dostane k vzájemnému propojení s jinými typy volání v systému. Pokud už například probíhá aktivní telefonní hovor, CXCallController
může aplikaci VOIP povolit, aby hovor přidržel a spustil nebo zvednul hovor VOIP.
Aplikace by měla používat CXCallController
následující:
- Nahlásit, když uživatel spustil odchozí hovor do systému.
- Nahlásit, když uživatel odpoví na příchozí hovor do systému.
- Nahlašte, když uživatel ukončí volání systému.
Když chce aplikace komunikovat s místním uživatelem akce systému, používá CXTransaction
třídu:
Implementace CallKitu
Následující části ukazují, jak implementovat CallKit v aplikaci VOIP Xamarin.iOS. V zájmu příkladu bude tento dokument používat kód z fiktivní aplikace MonkeyCall VOIP. Zde uvedený kód představuje několik podpůrných tříd, konkrétní části CallKit budou podrobně popsány v následujících částech.
ActiveCall – třída
Třída je používána ActiveCall
aplikací MonkeyCall k uložení všech informací o volání 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é lze vyvolat při změně stavu volání. Vzhledem k tomu, že se jedná pouze o příklad, existují tři metody, které se používají k simulaci spuštění, odpovídání a ukončení volání.
StartCallRequest – třída
StartCallRequest
Statická třída poskytuje několik pomocných metod, které se použijí 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 CallHandleFromURL
a CallHandleFromActivity
třídy se používají v AppDelegate k získání popisovače kontaktu osoby, která se volá v odchozím hovoru. Další informace najdete v části Zpracování odchozích hovorů níže.
Třída ActiveCallManager
Třída ActiveCallManager
zpracovává všechna otevřená volání v aplikaci 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
}
}
Opět platí, že protože se jedná pouze o simulaci, ActiveCallManager
udržuje jedinou kolekci ActiveCall
objektů a má rutinu pro vyhledání daného volání podle jeho UUID
vlastnosti. Zahrnuje také metody zahájení, ukončení a změny stavu blokování odchozího hovoru. Další informace najdete v části Zpracování odchozích hovorů níže.
ProviderDelegate – třída
Jak je popsáno výše, poskytuje obousměrnou CXProvider
komunikaci mezi aplikací a systémem pro oznámení mimo pásmo. Vývojář musí poskytnout vlastní CXProviderDelegate
a připojit ho k CXProvider
aplikaci, aby mohla zpracovávat vzdálené události CallKit. MonkeyCall používá následující 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
}
}
Když se vytvoří instance tohoto delegáta, předá ActiveCallManager
se mu, že bude zpracovávat všechny aktivity volání. Dále definuje typy popisovačů (CXHandleType
), na které CXProvider
bude reagovat:
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
A načte 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
sady, 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ří novou CXProvider
s těmito konfiguracemi a připojí se k němu:
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
Když používáte CallKit, aplikace už nebude vytvářet a zpracovávat vlastní zvukové relace, místo toho bude muset nakonfigurovat a používat zvukovou relaci, kterou systém vytvoří a zpracuje pro ni.
Pokud by se jednalo o skutečnou aplikaci, DidActivateAudioSession
metoda by se použila ke spuštění volání s předem nakonfigurovaným AVAudioSession
systémem, který poskytl:
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
Metoda by také použila DidDeactivateAudioSession
k dokončení a uvolnění jeho připojení k zadané zvukové relaci systému:
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.
AppDelegate – třída
MonkeyCall používá AppDelegate k uložení instancí ActiveCallManager
a CXProviderDelegate
které se použijí v celé aplikaci:
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 OpenUrl
a ContinueUserActivity
přepsání se používají, když aplikace zpracovává odchozí hovor. Další informace najdete v části Zpracování odchozích hovorů níže.
Zpracování příchozích hovorů
Existuje několik stavů a procesů, kterými může příchozí volání VOIP projít během typického pracovního postupu příchozího volání, například:
- Informuje uživatele (a systém), že existuje příchozí hovor.
- Když chce uživatel přijmout hovor a inicializovat hovor s jiným uživatelem, obdrží oznámení.
- 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 pomocí CallKitu zpracovat pracovní postup příchozího volání, a to znovu pomocí aplikace MonkeyCall VOIP jako příklad.
Informování uživatele o příchozím hovoru
Když vzdálený uživatel spustil konverzaci VOIP s místním uživatelem, dojde k následujícímu:
- Aplikace obdrží oznámení ze své komunikační sítě, že existuje příchozí volání VOIP.
- Aplikace používá
CXProvider
k odesláníCXCallUpdate
do systému zprávu o volání. - Systém publikuje volání do systémového uživatelského rozhraní, systémových služeb a všech dalších aplikací VOIP pomocí CallKitu.
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í k němu popisovač, který identifikuje volajícího. Dále používá ReportNewIncomingCall
metodu CXProvider
třídy k informování systému volání. Pokud je úspěšné, volání se přidá do kolekce aktivních volání aplikace, pokud ne, musí být uživateli nahlášena chyba.
Příjem příchozího hovoru uživatelem
Pokud chce uživatel přijmout příchozí volání VOIP, dojde k následujícímu:
- Uživatelské rozhraní systému informuje systém, že uživatel chce přijmout volání VOIP.
- Systém pošle
CXAnswerCallAction
aplikaciCXProvider
informace o záměru odpovědi. - Aplikace informuje svou komunikační síť, že uživatel odpovídá na hovor 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í nenašlo, systém se upozorní a metoda se ukončí. Pokud je nalezena, AnswerCall
metoda ActiveCall
třídy je volána ke spuštění volání a systém je informace, pokud je úspěšná nebo neúspěšná.
Uživatel, který končí příchozí hovor
Pokud chce uživatel ukončit volání z uživatelského rozhraní aplikace, dojde k následujícímu:
- Aplikace vytvoří
CXEndCallAction
, která se spojí doCXTransaction
systému, aby informovala, že volání končí. - Systém ověří záměr koncového volání a přesměruje
CXEndCallAction
zpět do aplikaceCXProvider
. - Aplikace pak informuje svou komunikační síť, že volání končí.
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í nenašlo, systém se upozorní a metoda se ukončí. Pokud je nalezena, EndCall
metoda ActiveCall
třídy je volána k ukončení volání a systém je informace, pokud je úspěšná nebo selže. V případě úspěchu se volání odebere z kolekce aktivních volání.
Správa více volání
Většina aplikací VOIP dokáže zpracovat více volání najednou. Pokud je aktuálně aktivní hovor VOIP a aplikace obdrží oznámení, že existuje nový příchozí hovor, může uživatel pozastavit nebo zavěsit při prvním hovoru, aby odpověděl na druhý hovor.
V situaci výše odešle CXTransaction
systém aplikaci, která bude obsahovat seznam více akcí (například a CXEndCallAction
CXAnswerCallAction
). Všechny tyto akce musí být splněny jednotlivě, aby systém mohl odpovídajícím způsobem aktualizovat uživatelské rozhraní.
Zpracování odchozích hovorů
Pokud uživatel klepne na položku ze seznamu Poslední (v aplikaci Telefon), například z volání, které patří do aplikace, odešle systém záměr zahájení volání:
- Aplikace vytvoří akci zahájit volání na základě záměru zahájit volání, který přijal ze systému.
- Aplikace použije
CXCallController
k vyžádání akce zahájit volání ze systému. - Pokud systém akce přijme, vrátí se do aplikace prostřednictvím delegáta
XCProvider
. - Aplikace spustí odchozí hovor se svou komunikační sítí.
Další informace o záměrech najdete v naší dokumentaci k rozšířením záměrů a záměrů uživatelského rozhraní .
Životní cyklus odchozích hovorů
Při práci s CallKitem a odchozím voláním bude aplikace muset informovat systém o následujících událostech životního cyklu:
- Spuštění – Informujte systém, že odchozí hovor se chystá zahájit.
- Spuštěno – Informujte systém, že se spustil odchozí hovor.
- Připojení - Informujte systém, že se odchozí hovor připojuje.
- Připojení ed - Informujte odchozí hovor, který je připojený a že obě strany mohou mluvit nyní.
Například následující kód spustí odchozí hovor:
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);
}
CXHandle
Vytvoří a použije ho ke konfiguraciCXStartCallAction
, která je součástí balíčku do CXTransaction
systému, který je odeslán do systému pomocí RequestTransaction
metody CXCallController
třídy. Voláním RequestTransaction
metody může systém umístit všechna existující volání do blokování, bez ohledu na zdroj (Telefon aplikace, FaceTime, VOIP atd.), před spuštěním nového volání.
Požadavek na zahájení odchozího hovoru VOIP může pocházet z několika různých zdrojů, jako je Siri, záznam na kartě kontaktu (v aplikaci Kontakty) nebo ze seznamu Poslední (v aplikaci Telefon). V těchto situacích se aplikaci odešle záměr zahájení volání uvnitř NSUserActivity
a AppDelegate ji 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 metoda CallHandleFromActivity
pomocné třídy StartCallRequest
se používá k získání popisovače pro osobu, která je volána (viz StartCallRequest Třída výše).
PerformStartCallAction
Metoda ProviderDelegate Třída se používá k konečně spuštění skutečného odchozího volání a informovat systém 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ání informací o probíhajícím hovoru) a naplní volající osobu. ConnectedChanged
Události StartingConnectionChanged
se používají k monitorování a hlášení životního cyklu odchozích hovorů. Hovor se spustí a systém informuje, že akce byla splněna.
Ukončení odchozího hovoru
Jakmile uživatel dokončí odchozí hovor 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);
}
Pokud vytvoří CXEndCallAction
identifikátor UUID volání na konec, spojí ho do CXTransaction
systému, který se odešle do systému pomocí RequestTransaction
metody CXCallController
třídy.
Další podrobnosti o CallKitu
Tato část se věnuje několika dalším podrobnostem, které bude vývojář muset vzít v úvahu při práci s CallKitem, například:
- Konfigurace zprostředkovatele
- Chyby akcí
- Systémová omezení
- Zvuk VOIP
Konfigurace zprostředkovatele
Konfigurace zprostředkovatele umožňuje aplikaci VOIP pro iOS 10 přizpůsobit uživatelské prostředí (v nativním uživatelském rozhraní pro volání) při práci s CallKitem.
Aplikace může provádět následující typy přizpůsobení:
- Zobrazí lokalizovaný název.
- Povolte podporu videohovorů.
- Přizpůsobte tlačítka uživatelského rozhraní v uživatelském rozhraní pro volání tak, že předáte vlastní ikonu obrázku šablony. Interakce uživatele s vlastními tlačítky se odesílá přímo do aplikace, která se má zpracovat.
Chyby akcí
Aplikace VOIP pro iOS 10 využívající CallKit musí zpracovávat akce, které selhávají, a udržovat uživatele neustále informovaný o stavu akce.
Vezměte v úvahu následující příklad:
- Aplikace přijala akci zahájení volání a zahájila proces inicializace nového volání VOIP se svou komunikační sítí.
- Kvůli omezené nebo žádné možnosti síťové komunikace se toto připojení nezdaří.
- Aplikace musí odeslat zprávu o navrácení služeb po obnovení zpět do akce Zahájení volání (
Action.Fail()
), aby informovala systém o selhání. - To umožňuje systému informovat uživatele o stavu volání. Pokud například chcete zobrazit uživatelské rozhraní selhání volání.
Aplikace IOS 10 VOIP navíc bude muset reagovat na chyby časového limitu, ke kterým může dojít v případě, že očekávanou akci nelze zpracovat v daném časovém intervalu. Každý typ akce, který poskytuje CallKit, má přidruženou maximální hodnotu časového limitu. Tyto hodnoty časového limitu zajišťují, aby se všechny akce CallKit požadované uživatelem zpracovávaly responzivním způsobem, a proto zachovávají tekutinu a odezvu operačního systému.
Existuje několik metod pro delegáta zprostředkovatele (CXProviderDelegate
), které by se měly přepsat, aby se řádně zvládly i tyto situace časového limitu.
Systémová omezení
Na základě aktuálního stavu zařízení s iOSem, na kterém běží aplikace VOIP pro iOS 10, se můžou vynutit určitá systémová omezení.
Například příchozí volání VOIP může systém omezit, pokud:
- Volající je v seznamu blokovaných volajících uživatele.
- Zařízení s iOSem uživatele je v režimu Do-Not-Disturb.
Pokud je volání VOIP omezeno některým z těchto situací, použijte k jeho zpracování následující kód:
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 nabízí 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 při spuštění v iOSu 10 zvýšenou prioritu. Tato úroveň priority je stejná jako předdefinovaná aplikace Telefon a FaceTime a tato rozšířená úroveň priority zabrání ostatním spuštěným aplikacím přerušit zvukovou relaci aplikace VOIP.
Kromě toho má CallKit přístup k dalším tipům směrování zvuku, které můžou zvýšit výkon a inteligentně směrovat zvuk VOIP na konkrétní výstupní zařízení během živého hovoru na základě uživatelských předvoleb a stavů zařízení. Například na základě připojených zařízení, jako jsou sluchátka Bluetooth, živé připojení CarPlay nebo nastavení přístupnosti.
Během životního cyklu typického volání VOIP pomocí CallKitu bude aplikace muset nakonfigurovat zvukový stream, který mu CallKit poskytne. Podívejte se na následující příklad:
- Aplikace přijme akci zahájení hovoru, aby odpověděla na příchozí hovor.
- Před splněním této akce aplikace poskytuje konfiguraci, která bude pro svou
AVAudioSession
aplikaci vyžadovat . - Aplikace informuje systém, že akce byla splněna.
- Než se hovor připojí, CallKit poskytuje vysokou prioritu
AVAudioSession
odpovídající konfiguraci, kterou aplikace požadovala. Aplikace bude upozorněna metodouDidActivateAudioSession
jehoCXProviderDelegate
.
Práce s rozšířeními adresáře volání
Při práci s CallKitem poskytují rozšíření adresářů volání způsob, jak přidat blokovaná telefonní čísla a identifikovat čísla specifická pro danou aplikaci VOIP kontaktům v aplikaci Kontakt na zařízení s iOSem.
Implementace rozšíření adresáře volání
Pokud chcete implementovat rozšíření adresáře volání v aplikaci Xamarin.iOS, postupujte takto:
Otevřete řešení aplikace v Visual Studio pro Mac.
Klikněte pravým tlačítkem na název řešení v Průzkumník řešení a vyberte Přidat>nový projekt.
Vyberte rozšíření rozšíření>pro rozšíření iOS>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 projektu nebo název řešení a klikněte na tlačítko Vytvořit:
Tím do projektu přidáte CallDirectoryHandler.cs
třídu, která vypadá takto:
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
}
}
Metoda BeginRequest
v obslužné rutině adresáře volání bude nutné upravit tak, aby poskytovala požadované funkce. V případě výše uvedeného vzorku se pokusí nastavit seznam blokovaných a dostupných čísel v databázi kontaktů aplikace VOIP. Pokud některý z těchto požadavků z nějakého důvodu selže, vytvořte popis NSError
selhání a předejte ji CancelRequest
metodu CXCallDirectoryExtensionContext
třídy.
K nastavení blokovaných čísel použijte AddBlockingEntry
metodu CXCallDirectoryExtensionContext
třídy. Čísla zadaná metodě musí být v číselném vzestupném pořadí. Pokud chcete dosáhnout optimálního výkonu a využití paměti, pokud existuje mnoho telefonních čísel, zvažte načtení pouze podmnožinu čísel v daném okamžiku a použití automatických fondů k uvolnění objektů přidělených během každé dávky čísel, která jsou načtena.
Chcete-li informovat aplikaci Contact o kontaktních číslech známých pro aplikaci VOIP, použijte AddIdentificationEntry
metodu CXCallDirectoryExtensionContext
třídy a zadejte číslo i identifikační popisek. Opět musí být čísla zadaná metodě v číselném vzestupném pořadí. Pokud chcete dosáhnout optimálního výkonu a využití paměti, pokud existuje mnoho telefonních čísel, zvažte načtení pouze podmnožinu čísel v daném okamžiku a použití automatických fondů k uvolnění objektů přidělených během každé dávky čísel, která jsou načtena.
Shrnutí
Tento článek se zabývá novým rozhraním CallKit API, které apple vydal v iOS 10 a jak ho implementovat v aplikacích Xamarin.iOS VOIP. Ukázalo se, jak CallKit umožňuje integraci aplikace do systému iOS, jak poskytuje paritu funkcí s integrovanými aplikacemi (například Telefon) a jak zvyšuje viditelnost aplikace v celém iOSu v umístěních, jako jsou zamykací a domovské obrazovky, prostřednictvím interakcí Siri a aplikací Kontakty.