CallKit w środowisku Xamarin.iOS
Nowy interfejs API zestawu CallKit w systemie iOS 10 umożliwia aplikacjom VOIP integrację z interfejsem użytkownika i Telefon oraz udostępnia znany interfejs i środowisko użytkownikowi końcowemu. Dzięki temu interfejsowi API użytkownicy mogą wyświetlać i korzystać z wywołań VOIP z ekranu blokady urządzenia z systemem iOS oraz zarządzać kontaktami przy użyciu widoków Ulubione i Ostatnie w aplikacji Telefon.
Zestaw CallKit — informacje
Według firmy Apple, CallKit to nowa struktura, która podniesie poziom aplikacji Voice Over IP (VOIP) innych firm do środowiska 1st party w systemie iOS 10. Interfejs API zestawu CallKit umożliwia aplikacjom VOIP integrację z interfejsem użytkownika i Telefon oraz udostępnia znajomy interfejs i środowisko użytkownikowi końcowemu. Podobnie jak wbudowana aplikacja Telefon, użytkownik może wyświetlać i korzystać z wywołań VOIP z ekranu blokady urządzenia z systemem iOS oraz zarządzać kontaktami przy użyciu widoków Ulubione i Ostatnie w aplikacji Telefon.
Ponadto interfejs API CallKit umożliwia tworzenie rozszerzeń aplikacji, które mogą skojarzyć numer telefonu z nazwą (identyfikatorem osoby wywołującej) lub poinformować system, kiedy numer powinien zostać zablokowany (blokowanie wywołań).
Istniejące środowisko aplikacji VOIP
Przed omówieniem nowego interfejsu API CallKit i jego możliwości zapoznaj się z bieżącym środowiskiem użytkownika z aplikacją VOIP innej firmy w systemie iOS 9 (i mniejszym) przy użyciu fikcyjnej aplikacji VOIP o nazwie MonkeyCall. MonkeyCall to prosta aplikacja, która umożliwia użytkownikowi wysyłanie i odbieranie wywołań VOIP przy użyciu istniejących interfejsów API systemu iOS.
Obecnie, jeśli użytkownik odbiera połączenie przychodzące na MonkeyCall i ich i Telefon jest zablokowany, powiadomienie odebrane na ekranie Blokada jest nie do odróżnienia od dowolnego innego typu powiadomienia (na przykład z aplikacji Wiadomości lub Poczty).
Jeśli użytkownik chciał odpowiedzieć na połączenie, musiałby przesunąć powiadomienie MonkeyCall, aby otworzyć aplikację i wprowadzić kod dostępu (lub identyfikator Touch ID użytkownika), aby odblokować telefon, zanim będzie mógł zaakceptować połączenie i rozpocząć konwersację.
Doświadczenie jest równie kłopotliwe, jeśli telefon jest odblokowany. Ponownie przychodzące wywołanie MonkeyCall jest wyświetlane jako standardowy baner powiadomień, który przesuwa się z góry ekranu. Ponieważ powiadomienie jest tymczasowe, można go łatwo przegapić przez użytkownika zmuszając ich do otwarcia Centrum powiadomień i znalezienia konkretnego powiadomienia, aby odpowiedzieć, a następnie wywołać lub znaleźć i uruchomić aplikację MonkeyCall ręcznie.
Środowisko aplikacji VoIP zestawu CallKit
Implementując nowe interfejsy API CallKit w aplikacji MonkeyCall, środowisko użytkownika z przychodzącym wywołaniem VOIP można znacznie ulepszyć w systemie iOS 10. Zapoznaj się z przykładem użytkownika odbierającego połączenie VOIP, gdy jego telefon jest zablokowany z góry. Zaimplementowanie zestawu CallKit spowoduje wyświetlenie wywołania na ekranie blokady urządzenia i Telefon tak samo jak w przypadku odebrania wywołania z wbudowanej aplikacji Telefon z pełnoekranowym, natywnym interfejsem użytkownika i standardowymi funkcjami szybkiego przesunięcia do odpowiedzi.
Ponownie, jeśli i Telefon jest odblokowany po odebraniu wywołania VOIP MonkeyCall, ten sam pełny ekran, natywny interfejs użytkownika i standardowe szybkie przesunięcie do odpowiedzi i naciśnięcie do odrzucenia funkcjonalności wbudowanej aplikacji Telefon jest prezentowany, a MonkeyCall ma możliwość odtwarzania niestandardowego dzwonka.
Zestaw CallKit udostępnia dodatkowe funkcje dla aplikacji MonkeyCall, co umożliwia interakcję wywołań VOIP z innymi typami wywołań, wyświetlanie ich we wbudowanych listach Ostatnie i Ulubione w celu korzystania z wbudowanych funkcji Nie przeszkadzać i blokuj, uruchamiania wywołań MonkeyCall z Siri i oferuje użytkownikom możliwość przypisywania wywołań MonkeyCall do osób w aplikacji Kontakty.
W poniższych sekcjach opisano architekturę CallKit, przepływy połączeń przychodzących i wychodzących oraz szczegółowo interfejs API CallKit.
Architektura zestawu CallKit
W systemie iOS 10 firma Apple przyjęła zestaw CallKit we wszystkich usługach systemowych, takich jak wywołania wykonywane na urządzeniu CarPlay, na przykład, są znane interfejsowi użytkownika systemu za pośrednictwem zestawu CallKit. W poniższym przykładzie, ponieważ aplikacja MonkeyCall przyjmuje zestaw CallKit, jest znana systemowi w taki sam sposób jak te wbudowane usługi systemowe i pobiera wszystkie te same funkcje:
Przyjrzyj się bliżej aplikacji MonkeyCall z powyższego diagramu. Aplikacja zawiera cały kod do komunikowania się z własną siecią i zawiera własne interfejsy użytkownika. Łączy się on z zestawem CallKit w celu komunikowania się z systemem:
W zestawie CallKit istnieją dwa główne interfejsy używane przez aplikację:
CXProvider
- Umożliwia to aplikacji MonkeyCall informowanie systemu o wszelkich powiadomieniach poza pasmem, które mogą wystąpić.CXCallController
— Umożliwia aplikacji MonkeyCall informowanie systemu akcji użytkownika lokalnego.
Dostawca CXProvider
Jak wspomniano powyżej, CXProvider
umożliwia aplikacji informowanie systemu o wszelkich powiadomieniach poza pasmem, które mogą wystąpić. Są to powiadomienia, które nie występują z powodu akcji użytkownika lokalnego, ale występują z powodu zdarzeń zewnętrznych, takich jak połączenia przychodzące.
Aplikacja powinna używać elementu CXProvider
dla następujących elementów:
- Zgłoś przychodzące wywołanie do systemu.
- Zgłoś, że połączenie wychodzące zostało połączone z systemem.
- Zgłoś użytkownika zdalnego kończącego wywołanie systemu.
Gdy aplikacja chce komunikować się z systemem, używa klasy i kiedy system musi komunikować się z aplikacją, używa CXCallUpdate
CXAction
klasy :
The CXCallController
Aplikacja CXCallController
umożliwia aplikacji informowanie systemu akcji użytkownika lokalnego, takich jak uruchamianie wywołania VOIP przez użytkownika. Implementacja CXCallController
aplikacji umożliwia współdziałanie z innymi typami wywołań w systemie. Jeśli na przykład istnieje już aktywne wywołanie telefonii, CXCallController
może zezwolić aplikacji VOIP na wstrzymanie tego wywołania i rozpoczęcie lub odbieranie połączenia VOIP.
Aplikacja powinna używać elementu CXCallController
dla następujących elementów:
- Zgłoś, gdy użytkownik uruchomił połączenie wychodzące do systemu.
- Zgłoś, gdy użytkownik odpowie na przychodzące wywołanie systemu.
- Zgłoś, gdy użytkownik kończy wywołanie systemu.
Gdy aplikacja chce przekazać lokalne akcje użytkownika do systemu, używa CXTransaction
klasy :
Implementowanie zestawu CallKit
W poniższych sekcjach pokazano, jak zaimplementować zestaw CallKit w aplikacji VOIP platformy Xamarin.iOS. Na przykład ten dokument będzie używać kodu z fikcyjnej aplikacji MonkeyCall VOIP. Przedstawiony tutaj kod reprezentuje kilka klas pomocniczych. Elementy specyficzne dla zestawu CallKit zostaną szczegółowo omówione w poniższych sekcjach.
Klasa ActiveCall
Klasa ActiveCall
jest używana przez aplikację MonkeyCall do przechowywania wszystkich informacji o wywołaniu VOIP, który jest obecnie aktywny w następujący sposób:
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
zawiera kilka właściwości definiujących stan wywołania i dwa zdarzenia, które mogą być wywoływane po zmianie stanu wywołania. Ponieważ jest to tylko przykład, istnieją trzy metody używane do symulowania uruchamiania, odpowiadania i kończenia wywołania.
Klasa StartCallRequest
Klasa statyczna StartCallRequest
udostępnia kilka metod pomocnika, które będą używane podczas pracy z wywołaniami wychodzącymi:
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;
}
}
}
}
Klasy CallHandleFromURL
i CallHandleFromActivity
są używane w aplikacji AppDelegate, aby uzyskać dojście kontaktowe osoby wywoływanej w wywołaniu wychodzącym. Aby uzyskać więcej informacji, zobacz sekcję Obsługa połączeń wychodzących poniżej.
Klasa ActiveCallManager
Klasa ActiveCallManager
obsługuje wszystkie otwarte wywołania w aplikacji 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
}
}
Ponownie, ponieważ jest to tylko symulacja, ActiveCallManager
jedyny utrzymuje kolekcję ActiveCall
obiektów i ma procedurę znajdowania danego wywołania przez jego UUID
właściwość. Zawiera również metody uruchamiania, kończenia i zmieniania stanu wstrzymania wywołania wychodzącego. Aby uzyskać więcej informacji, zobacz sekcję Obsługa połączeń wychodzących poniżej.
Klasa ProviderDelegate
Jak wspomniano powyżej, zapewnia dwukierunkową CXProvider
komunikację między aplikacją a systemem na potrzeby powiadomień poza pasmem. Deweloper musi podać niestandardowy element CXProviderDelegate
i dołączyć go do CXProvider
aplikacji, aby obsłużyć zdarzenia CallKit poza pasmem. Aplikacja MonkeyCall używa następującego CXProviderDelegate
elementu :
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
}
}
Po utworzeniu wystąpienia tego delegata zostanie ono ActiveCallManager
przekazane, które będzie używane do obsługi dowolnego działania wywołania. Następnie definiuje typy dojść (CXHandleType
), na które CXProvider
będą odpowiadać:
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
Pobiera obraz szablonu, który zostanie zastosowany do ikony aplikacji, gdy wywołanie jest w toku:
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
Te wartości są dołączane do elementu CXProviderConfiguration
, który będzie używany do konfigurowania elementu CXProvider
:
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
Delegat następnie tworzy nowy CXProvider
z tymi konfiguracjami i dołącza się do niego:
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
W przypadku korzystania z zestawu CallKit aplikacja nie będzie już tworzyć i obsługiwać własnych sesji audio, zamiast tego będzie musiała skonfigurować i użyć sesji audio, którą system utworzy i obsłuży dla niej.
Gdyby była to prawdziwa aplikacja, DidActivateAudioSession
metoda zostanie użyta do uruchomienia wywołania przy użyciu wstępnie skonfigurowanego AVAudioSession
przez system:
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
Za pomocą DidDeactivateAudioSession
metody można również sfinalizować i zwolnić połączenie z sesją audio dostarczonej przez system:
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// releated audio
}
Pozostała część kodu zostanie szczegółowo omówiona w kolejnych sekcjach.
Klasa AppDelegate
Aplikacja MonkeyCall używa elementu AppDelegate do przechowywania wystąpień ActiveCallManager
elementu i CXProviderDelegate
, które będą używane w całej aplikacji:
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
i ContinueUserActivity
zastąpienia są używane, gdy aplikacja przetwarza wywołanie wychodzące. Aby uzyskać więcej informacji, zobacz sekcję Obsługa połączeń wychodzących poniżej.
Obsługa połączeń przychodzących
Istnieje kilka stanów i procesów, przez które przychodzące wywołanie VOIP może przechodzić podczas typowego przepływu pracy wywołania przychodzącego, takiego jak:
- Informowanie użytkownika (i systemu) o tym, że istnieje wywołanie przychodzące.
- Otrzymywanie powiadomienia, gdy użytkownik chce odpowiedzieć na połączenie i zainicjować połączenie z innym użytkownikiem.
- Poinformuj system i sieć komunikacji, gdy użytkownik chce zakończyć bieżące wywołanie.
W poniższych sekcjach opisano szczegółowo, jak aplikacja może używać zestawu CallKit do obsługi przepływu pracy wywołań przychodzących, ponownie przy użyciu aplikacji MonkeyCall VOIP jako przykładu.
Informowanie użytkownika o połączeniu przychodzącym
Gdy użytkownik zdalny rozpoczął rozmowę VOIP z użytkownikiem lokalnym, następuje:
- Aplikacja otrzymuje powiadomienie z sieci komunikacji, że istnieje przychodzące wywołanie VOIP.
- Aplikacja używa elementu
CXProvider
, aby wysłać element doCXCallUpdate
systemu, informując go o wywołaniu. - System publikuje wywołanie interfejsu użytkownika systemu, usług systemowych i innych aplikacji VOIP przy użyciu zestawu CallKit.
Na przykład w pliku 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);
}
});
}
Ten kod tworzy nowe CXCallUpdate
wystąpienie i dołącza do niego uchwyt, który zidentyfikuje obiekt wywołujący. Następnie używa ReportNewIncomingCall
metody CXProvider
klasy do informowania systemu o wywołaniu. Jeśli to się powiedzie, wywołanie zostanie dodane do kolekcji aktywnych wywołań aplikacji, jeśli tak nie jest, błąd musi zostać zgłoszony użytkownikowi.
Użytkownik odbierający połączenie przychodzące
Jeśli użytkownik chce odpowiedzieć na przychodzące wywołanie VOIP, następuje:
- Interfejs użytkownika systemu informuje system, że użytkownik chce odpowiedzieć na wywołanie VOIP.
- System wysyła element
CXAnswerCallAction
do aplikacjiCXProvider
z informacją o intencji odpowiedzi. - Aplikacja informuje sieć komunikacji, że użytkownik odpowiada na połączenie, a wywołanie VOIP przebiega jak zwykle.
Na przykład w pliku 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 ();
}
});
}
Ten kod najpierw wyszukuje podane wywołanie na liście aktywnych wywołań. Jeśli nie można odnaleźć wywołania, system zostanie powiadomiony i metoda zakończy działanie. Jeśli zostanie znaleziona, metoda ActiveCall
klasy jest wywoływana w AnswerCall
celu uruchomienia wywołania, a system zawiera informacje, jeśli zakończy się powodzeniem lub niepowodzeniem.
Użytkownik kończący połączenie przychodzące
Jeśli użytkownik chce zakończyć wywołanie z poziomu interfejsu użytkownika aplikacji, wystąpią następujące czynności:
- Aplikacja tworzy
CXEndCallAction
pakiet, który jest wysyłanyCXTransaction
do systemu, aby poinformować o zakończeniu wywołania. - System weryfikuje
CXEndCallAction
intencję wywołania końcowego i wysyła z powrotem do aplikacji za pośrednictwem elementuCXProvider
. - Następnie aplikacja informuje sieć komunikacji o zakończeniu wywołania.
Na przykład w pliku 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 ();
}
});
}
Ten kod najpierw wyszukuje podane wywołanie na liście aktywnych wywołań. Jeśli nie można odnaleźć wywołania, system zostanie powiadomiony i metoda zakończy działanie. Jeśli zostanie znaleziona, metoda ActiveCall
klasy jest wywoływana w EndCall
celu zakończenia wywołania, a system zawiera informacje, jeśli zakończy się powodzeniem lub niepowodzeniem. Jeśli operacja zakończy się pomyślnie, wywołanie zostanie usunięte z kolekcji aktywnych wywołań.
Zarządzanie wieloma wywołaniami
Większość aplikacji VOIP może obsługiwać wiele wywołań jednocześnie. Jeśli na przykład istnieje aktywne wywołanie VOIP, a aplikacja otrzymuje powiadomienie o tym, że istnieje nowe połączenie przychodzące, użytkownik może wstrzymać lub zawiesić się przy pierwszym wywołaniu, aby odpowiedzieć na drugie.
W powyższej sytuacji system wyśle CXTransaction
element do aplikacji, która będzie zawierać listę wielu akcji (takich jak CXEndCallAction
i CXAnswerCallAction
). Wszystkie te akcje należy wykonać indywidualnie, aby system mógł odpowiednio zaktualizować interfejs użytkownika.
Obsługa połączeń wychodzących
Jeśli użytkownik naciśnie wpis z listy Ostatnie (w aplikacji Telefon), na przykład z wywołania należącego do aplikacji, zostanie wysłany intencja wywołania początkowego przez system:
- Aplikacja utworzy akcję wywołania początkowego na podstawie intencji wywołania początkowego otrzymanego z systemu.
- Aplikacja będzie używać polecenia ,
CXCallController
aby zażądać akcji Rozpoczęcia wywołania z systemu. - Jeśli system zaakceptuje akcję, zostanie on zwrócony do aplikacji za pośrednictwem delegata
XCProvider
. - Aplikacja uruchamia połączenie wychodzące z siecią komunikacji.
Aby uzyskać więcej informacji na temat intencji, zobacz dokumentację rozszerzenia interfejsu użytkownika intencji i intencji .
Cykl życia połączeń wychodzących
Podczas pracy z zestawem CallKit i wywołaniem wychodzącym aplikacja musi poinformować system o następujących zdarzeniach cyklu życia:
- Uruchamianie — informuje system, że zostanie uruchomione wywołanie wychodzące.
- Rozpoczęto — informuje system, że uruchomiono połączenie wychodzące.
- Połączenie — informuje system, że połączenie wychodzące jest nawiązywane.
- Połączenie - Poinformuj, że połączenie wychodzące nawiązało połączenie i że obie strony mogą teraz porozmawiać.
Na przykład następujący kod uruchomi wywołanie wychodzące:
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);
}
Tworzy element CXHandle
i używa go do skonfigurowania CXStartCallAction
elementu, który jest powiązany z elementem CXTransaction
wysyłanym do systemu przy użyciu RequestTransaction
metody CXCallController
klasy . Wywołując metodęRequestTransaction
, system może umieścić wszystkie istniejące wywołania w wstrzymaniu, niezależnie od źródła (Telefon aplikacji, FaceTime, VOIP itp.), przed rozpoczęciem nowego wywołania.
Żądanie uruchomienia wychodzącego wywołania VOIP może pochodzić z kilku różnych źródeł, takich jak Siri, wpis na karcie Kontakt (w aplikacji Kontakty) lub z listy Ostatnie (w aplikacji Telefon). W takich sytuacjach aplikacja zostanie wysłana intencją wywołania początkowego wewnątrz elementu , NSUserActivity
a element AppDelegate będzie musiał go obsłużyć:
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;
}
}
CallHandleFromActivity
Tutaj metoda klasy StartCallRequest
pomocniczej jest używana do pobrania dojścia do wywoływanej osoby (zobacz klasę StartCallRequest powyżej).
PerformStartCallAction
Metoda klasy ProviderDelegate służy do zakończenia rzeczywistego wywołania wychodzącego i informowania systemu o jego cyklu życia:
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 ();
}
});
}
Tworzy wystąpienie ActiveCall
klasy (w celu przechowywania informacji o wywołaniu w toku) i wypełnia się wywoływaną osobą. Zdarzenia StartingConnectionChanged
i ConnectedChanged
służą do monitorowania i raportowania cyklu życia połączeń wychodzących. Wywołanie zostało uruchomione, a system poinformował, że akcja została spełniona.
Kończenie połączenia wychodzącego
Gdy użytkownik zakończy połączenie wychodzące i chce go zakończyć, można użyć następującego kodu:
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 tworzy element CXEndCallAction
z identyfikatorem UUID wywołania do końca, tworzy go w CXTransaction
klasie, która jest wysyłana do systemu przy użyciu RequestTransaction
metody CXCallController
klasy .
Dodatkowe szczegóły zestawu CallKit
W tej sekcji omówiono dodatkowe szczegóły, które deweloper będzie musiał wziąć pod uwagę podczas pracy z zestawem CallKit, na przykład:
- Konfiguracja dostawcy
- Błędy akcji
- Ograniczenia systemowe
- Dźwięk VOIP
Konfiguracja dostawcy
Konfiguracja dostawcy umożliwia aplikacji VOIP dla systemu iOS 10 dostosowywanie środowiska użytkownika (wewnątrz natywnego interfejsu użytkownika wywołania) podczas pracy z zestawem CallKit.
Aplikacja może wprowadzać następujące typy dostosowań:
- Wyświetl zlokalizowaną nazwę.
- Włącz obsługę połączeń wideo.
- Dostosuj przyciski w interfejsie użytkownika wywołania, przedstawiając własną ikonę obrazu szablonu. Interakcja użytkownika z przyciskami niestandardowymi jest wysyłana bezpośrednio do aplikacji do przetworzenia.
Błędy akcji
Aplikacje voIP dla systemu iOS 10 korzystające z zestawu CallKit muszą obsługiwać akcje, które kończą się niepowodzeniem, i informować użytkownika o stanie akcji przez cały czas.
Weź pod uwagę następujący przykład:
- Aplikacja otrzymała akcję Rozpoczęcia połączenia i rozpoczęła proces inicjowania nowego wywołania VOIP za pomocą sieci komunikacyjnej.
- Ze względu na ograniczoną lub brak możliwości komunikacji sieciowej to połączenie kończy się niepowodzeniem.
- Aby poinformować system o awarii, aplikacja musi wysłaćkomunikat Niepowodzenie z powrotem do akcji Uruchom wywołanie (
Action.Fail()
). - Dzięki temu system może poinformować użytkownika o stanie wywołania. Aby na przykład wyświetlić interfejs użytkownika niepowodzenia wywołania.
Ponadto aplikacja VOIP dla systemu iOS 10 musi odpowiadać na błędy przekroczenia limitu czasu, które mogą wystąpić, gdy nie można przetworzyć oczekiwanej akcji w danym czasie. Każdy typ akcji dostarczony przez zestaw CallKit ma skojarzona maksymalną wartość limitu czasu. Te wartości limitu czasu zapewniają, że każda akcja CallKit żądana przez użytkownika jest obsługiwana w sposób dynamiczny, zapewniając płyn systemu operacyjnego i reagujący.
Istnieje kilka metod delegata dostawcy (CXProviderDelegate
), które powinny zostać zastąpione, aby bezpiecznie obsłużyć te sytuacje przekroczenia limitu czasu, jak również.
Ograniczenia systemowe
Na podstawie bieżącego stanu urządzenia z systemem iOS z uruchomioną aplikacją VOIP systemu iOS 10 można wymusić pewne ograniczenia systemowe.
Na przykład przychodzące wywołanie VOIP może być ograniczone przez system, jeśli:
- Osoba wywołująca znajduje się na liście zablokowanych rozmówców użytkownika.
- Urządzenie z systemem iOS użytkownika jest w trybie Nie przeszkadzać.
Jeśli wywołanie VOIP jest ograniczone przez dowolną z tych sytuacji, użyj następującego kodu, aby go obsłużyć:
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
}
}
});
}
}
Dźwięk VOIP
Zestaw CallKit zapewnia kilka korzyści związanych z obsługą zasobów audio, których aplikacja VOIP systemu iOS 10 będzie wymagała podczas połączenia VOIP na żywo. Jedną z największych korzyści jest sesja audio aplikacji będzie miała podwyższone priorytety podczas uruchamiania w systemie iOS 10. Jest to ten sam poziom priorytetu co wbudowane aplikacje Telefon i FaceTime, a ten rozszerzony poziom priorytetu uniemożliwi innym uruchomionym aplikacjom przerwanie sesji audio aplikacji VOIP.
Ponadto zestaw CallKit ma dostęp do innych wskazówek routingu audio, które mogą zwiększyć wydajność i inteligentnie kierować dźwięk VOIP do określonych urządzeń wyjściowych podczas połączenia na żywo na podstawie preferencji użytkownika i stanów urządzeń. Na przykład na podstawie dołączonych urządzeń, takich jak słuchawki Bluetooth, połączenie Na żywo CarPlay lub ustawienia ułatwień dostępu.
W trakcie cyklu życia typowego wywołania VOIP przy użyciu zestawu CallKit aplikacja będzie musiała skonfigurować strumień audio, który udostępni zestaw CallKit. Zapoznaj się z poniższym przykładem:
- Akcja rozpoczęcia połączenia jest odbierana przez aplikację w celu udzielenia odpowiedzi na połączenie przychodzące.
- Przed spełnieniem tej akcji przez aplikację udostępnia ona konfigurację, która będzie wymagana dla jej
AVAudioSession
elementu . - Aplikacja informuje system, że akcja została spełniona.
- Przed nawiązaniem połączenia zestaw CallKit zapewnia wysoki priorytet
AVAudioSession
zgodny z konfiguracją żądaną przez aplikację. Aplikacja zostanie powiadomionaDidActivateAudioSession
za pośrednictwem metody .CXProviderDelegate
Praca z rozszerzeniami katalogu wywołań
Podczas pracy z zestawem CallKit rozszerzenia katalogu wywołań umożliwiają dodawanie zablokowanych numerów wywołań i identyfikowanie numerów specyficznych dla danej aplikacji VOIP do kontaktów w aplikacji Kontakt na urządzeniu z systemem iOS.
Implementowanie rozszerzenia katalogu wywołań
Aby zaimplementować rozszerzenie katalogu wywołań w aplikacji platformy Xamarin.iOS, wykonaj następujące czynności:
Otwórz rozwiązanie aplikacji w Visual Studio dla komputerów Mac.
Kliknij prawym przyciskiem myszy nazwę rozwiązania w Eksplorator rozwiązań i wybierz polecenie Dodaj>nowy projekt.
Wybierz pozycję Rozszerzenia katalogu wywołania rozszerzeń> systemu iOS>, a następnie kliknij przycisk Dalej:
Wprowadź nazwę rozszerzenia i kliknij przycisk Dalej:
Dostosuj nazwę projektu i/lub nazwę rozwiązania, jeśli jest to wymagane, a następnie kliknij przycisk Utwórz:
Spowoduje to dodanie CallDirectoryHandler.cs
klasy do projektu, która wygląda następująco:
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
}
}
Aby BeginRequest
zapewnić wymaganą funkcjonalność, należy zmodyfikować metodę w procedurze obsługi katalogów wywołań. W przypadku powyższego przykładu próbuje ustawić listę zablokowanych i dostępnych numerów w bazie danych kontaktów aplikacji VOIP. Jeśli którekolwiek z żądań zakończy się niepowodzeniem z jakiegokolwiek powodu, utwórz element , NSError
aby opisać błąd i przekazać go do CancelRequest
metody CXCallDirectoryExtensionContext
klasy.
Aby ustawić zablokowane liczby, użyj AddBlockingEntry
metody CXCallDirectoryExtensionContext
klasy . Liczby podane w metodzie muszą być w kolejności liczbowej rosnącej. Aby uzyskać optymalną wydajność i użycie pamięci, jeśli istnieje wiele numerów telefonów, rozważ załadowanie tylko podzestawu liczb w danym momencie i użycie puli wersji automatycznych do zwolnienia obiektów przydzielonych podczas każdej partii numerów, które są ładowane.
Aby poinformować aplikację kontaktową o numerach kontaktowych znanych aplikacji VOIP, użyj AddIdentificationEntry
metody CXCallDirectoryExtensionContext
klasy i podaj zarówno numer, jak i etykietę identyfikującą. Ponownie liczby podane w metodzie muszą być w kolejności liczbowej rosnącej. Aby uzyskać optymalną wydajność i użycie pamięci, jeśli istnieje wiele numerów telefonów, rozważ załadowanie tylko podzestawu liczb w danym momencie i użycie puli wersji automatycznych do zwolnienia obiektów przydzielonych podczas każdej partii numerów, które są ładowane.
Podsumowanie
W tym artykule omówiono nowy interfejs API CallKit wydany przez firmę Apple w systemie iOS 10 oraz sposób implementacji go w aplikacjach VOIP platformy Xamarin.iOS. Pokazano, jak zestaw CallKit umożliwia aplikacji integrację z systemem iOS, jak zapewnia równoważność funkcji z wbudowanymi aplikacjami (takimi jak Telefon) i jak zwiększa widoczność aplikacji w całym systemie iOS w lokalizacjach, takich jak blokada i ekrany główne, za pośrednictwem interakcji Siri i aplikacji Kontakty.