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ásobník služby CallKit

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:

Architektura aplikaceCall

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 :

Komunikace se systémem přes poskytovatele CXProvider

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 :

Hlášení do systému pomocí CXCallController

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:

Vzdálený uživatel zahájil konverzaci VOIP

  1. Aplikace dostane ze své komunikační sítě oznámení, že došlo k příchozímu volání VOIP.
  2. Aplikace používá k CXProvider odeslání do CXCallUpdate systému informující o volání.
  3. 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:

Uživatel odpoví příchozí volání VOIP.

  1. Systémové uživatelské rozhraní informuje systém o tom, že uživatel chce odpovědět na volání VOIP.
  2. Systém pošle CXAnswerCallAction aplikaci, která bude informovat o CXProvider záměru odpovědi.
  3. 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:

Uživatel ukončí volání v uživatelském rozhraní aplikace.

  1. Aplikace vytvoří balíček CXEndCallAction , který je součástí nástroje CXTransaction , který je odeslán do systému za účelem informování o tom, že volání končí.
  2. Systém ověří záměr koncového volání a pošle CXEndCallAction zpátky do aplikace prostřednictvím CXProvider .
  3. 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í :

Přijetí záměru spustit volání

  1. 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.
  2. Aplikace bude používat CXCallController k žádosti o akci spustit hovor ze systému.
  3. Pokud systém přijme akci, bude vrácena do aplikace prostřednictvím XCProvider delegáta.
  4. 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:

  1. Od začátku – informujte systém, že se chystá spuštění odchozího volání.
  2. Začalo – informujte systém, zda bylo zahájeno odchozí volání.
  3. Připojování – informujte systém, že odchozí volání se připojuje.
  4. 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:

  1. Aplikace přijala akci spustit hovor a začala Postup inicializace nového volání VOIP v rámci komunikační sítě.
  2. Kvůli omezené nebo žádné síťové komunikační možnosti se připojení nezdařilo.
  3. Aplikace musí odeslat zprávu o selhání zpět do akce zahájit volání ( ), aby informovala systém o selhání.
  4. 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:

  1. Volající uživatel se nachází v seznamu blokovaných volajících uživatele.
  2. 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:

Sekvence akcí spustit volání

  1. Aplikace přijme akci spustit volání, aby odpovídala příchozímu volání.
  2. Než tuto akci splní aplikace, poskytne konfiguraci, která bude pro svůj požadavek vyžadovat AVAudioSession .
  3. Aplikace informuje systém o tom, že akce byla splněna.
  4. Před připojením volání CallKit poskytuje vysokou prioritu AVAudioSession odpovídající konfiguraci, kterou aplikace požaduje. Aplikace bude oznámena prostřednictvím DidActivateAudioSession metody CXProviderDelegate .

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:

  1. otevřete řešení aplikace v Visual Studio pro Mac.

  2. 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.

  3. Vyberte rozšíření pro iOSrozšířeníadresáře volání a klikněte na tlačítko Další :

    Vytváří se nové rozšíření adresáře volání.

  4. Zadejte název rozšíření a klikněte na tlačítko Další :

    Zadání názvu pro rozšíření

  5. v případě potřeby upravte název Project nebo název řešení a klikněte na tlačítko vytvořit :

    Vytváření projektu

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ů.