CallKit v Xamarin. iOSCallKit in Xamarin.iOS

Nové rozhraní CallKit API v iOS 10 poskytuje způsob, jak aplikace VOIP integrovat s uživatelským rozhraním iPhone a poskytnout koncovému uživateli známé rozhraní a možnosti.The new CallKit API in iOS 10 provides a way for VOIP apps to integrate with the iPhone UI and provide a familiar interface and experience to the end user. Díky tomuto uživateli rozhraní API si můžou na zamykací obrazovce zařízení s iOS zobrazit a interagovat s voláními VOIP a spravovat kontakty pomocí zobrazení oblíbených a posledních aplikací pro telefon.With this API users can view and interact with VOIP calls from the iOS device's Lock Screen and manage contacts using the Phone app's Favorites and Recents views.

O CallKitAbout CallKit

Podle společnosti Apple je CallKit novou architekturou, která bude zvyšovat úroveň aplikací VOIP (Voice over IP) třetích stran na základě zkušeností s iOS 10 od 1.According to Apple, CallKit is a new framework that will elevate 3rd party Voice Over IP (VOIP) apps to a 1st party experience on iOS 10. Rozhraní CallKit API umožňuje, aby se aplikace VOIP integrují s uživatelským rozhraním iPhonu a poskytovaly koncovému uživateli známé rozhraní a možnosti.The CallKit API allows VOIP apps to integrate with the iPhone UI and provide a familiar interface and experience to the end user. Stejně jako integrovaná aplikace pro telefon může uživatel zobrazit a interagovat s voláními VOIP z uzamčené obrazovky zařízení s iOS a spravovat kontakty pomocí zobrazení oblíbených a nedávných aplikací pro telefon.Just like the built-in Phone app, a user can view and interact with VOIP calls from the iOS device's Lock Screen and manage contacts using the Phone app's Favorites and Recents views.

Rozhraní CallKit API navíc poskytuje možnost vytvářet rozšíření aplikací, která můžou přidružit telefonní číslo k názvu (ID volajícího) nebo sdělit systému, že by mělo být blokované číslo (blokování volání).Additionally, the CallKit API provides the ability to create App Extensions that can associate a phone number with a name (Caller ID) or tell the system when a number should be blocked (Call Blocking).

Existující prostředí aplikace VOIPThe existing VOIP app experience

Než začnete diskutovat na nové rozhraní CallKit API a jeho schopnosti, podívejte se na aktuální uživatelské prostředí pomocí aplikace VOIP od jiného výrobce v iOS 9 (a menší) pomocí fiktivní aplikace VOIP s názvem MonkeyCall.Before discussing the new CallKit API and its abilities, take a look at the current user experience with a 3rd party VOIP app in iOS 9 (and lesser) using a fictitious VOIP app called MonkeyCall. MonkeyCall 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.MonkeyCall is a simple app that allows the user to send and receive VOIP calls using the existing iOS APIs.

V současné době platí, že pokud uživatel přijímá příchozí volání na MonkeyCall a jejich iPhone je uzamčeno, oznámení přijaté na zamykací obrazovce nerozlišuje jiný typ oznámení (například zprávy ze zpráv nebo e-mailových aplikací).Currently, if the user is receiving an incoming call on MonkeyCall and their iPhone is locked, the notification received on the Lock screen is indistinguishable from any other type of notification (like those from the Messages or Mail apps for example).

Pokud uživatel chtěl přijmout volání, musel si vyzvat oznámení MonkeyCall a otevřít aplikaci a zadat heslo (nebo ID dotyku uživatele), aby telefon odemkl, a mohl by hovor přijmout a spustit konverzaci.If the user wanted to answer the call, they'd have to slide the MonkeyCall notification to open the app and enter their passcode (or user Touch ID) to unlock the phone before they could accept the call and start the conversation.

Prostředí je stejně náročné, pokud je telefon odemčený.The experience is equally cumbersome if the phone is unlocked. Příchozí volání MonkeyCall se znovu zobrazí jako standardní nápis oznámení, který se snímí v horní části obrazovky.Again, the incoming MonkeyCall call is displayed as a standard notification banner that slides in from the top of the screen. Vzhledem k tomu, že oznámení je dočasné, může ho uživatel snadno vynutit, aby si buď otevřelo centrum oznámení, a našli konkrétní oznámení, aby odpověděli na odpověď, vyvolal nebo najde a spustí aplikaci MonkeyCall ručně.Since the notification is temporary, it can be easily missed by the user forcing them to either open the Notification Center and find the specific notification to answer then call or find and launch the MonkeyCall app manually.

Prostředí aplikace VOIP pro CallKitThe CallKit VOIP app experience

Díky implementaci nových rozhraní CallKit API v aplikaci MonkeyCall je možné výrazně zlepšit činnost uživatele s příchozím voláním VOIP v iOS 10.By implementing the new CallKit APIs in the MonkeyCall app, the user's experience with an incoming VOIP call can be greatly improved in iOS 10. Pokud je telefon zamčený od výše uvedeného telefonu, použijte příklad uživatele, který přijímá volání VOIP.Take the example of the user receiving a VOIP call when their phone is locked from above. Implementací CallKit se volání zobrazí na zamykací obrazovce iPhone stejně, jako by to bylo v případě, že bylo volání přijato z integrované aplikace pro telefon, pomocí nativního uživatelského rozhraní a funkce potáhnutí na odpověď.By implementing CallKit, the call will appear on the iPhone's Lock screen, just as it would if the call was being received from the built-in Phone app, with the full-screen, native UI and standard swipe-to-answer functionality.

Pokud je zařízení iPhone po přijetí volání MonkeyCall VOIP odemknuté, zobrazí se stejné funkce pro celou obrazovku, nativní uživatelské rozhraní a standardní uživatelské rozhraní, potáhnutí na odpověď a klepnutí na odpověď v integrované telefonní aplikaci. MonkeyCall má možnost přehrávání vlastního vyzváněcího tónu.Again, if the iPhone is unlocked when a MonkeyCall VOIP call is received, the same full-screen, native UI and standard swipe-to-answer and tap-to-decline functionality of the built-in Phone app is presented and MonkeyCall has the option of playing a custom ringtone.

CallKit poskytuje další funkce pro MonkeyCall, které umožní volání VOIP komunikovat s ostatními typy volání, aby se zobrazily v vestavěných a oblíbených seznamech, aby používaly integrované nerušitelné a blokující funkce, spouštěla MonkeyCall volání z Siri a nabízí možnost uživatelům přiřazovat volání MonkeyCall lidem v aplikaci Kontakty.CallKit provides additional functionality to MonkeyCall, allowing its VOIP calls to interact with other types of calls, to appear in the built in Recents and Favorite lists, to use the built-in Do Not Disturb and Block features, start MonkeyCall calls from Siri and offers the ability for users to assign MonkeyCall calls to people in the Contacts app.

V následujících částech se dozvíte, jak se CallKit architektura, toky příchozího a odchozího hovoru a rozhraní CallKit API.The following sections will cover the CallKit architecture, the incoming and outgoing call flows and the CallKit API in detail.

Architektura CallKitThe CallKit architecture

V systému iOS 10 přijal společnost Apple CallKit ve všech systémových službách, jako je například volání v uživatelském rozhraní CarPlay známé jako uživatelské rozhraní systému prostřednictvím CallKit.In iOS 10, Apple has adopted CallKit in all of the System Services such that calls made on CarPlay, for example, are known to the System UI via CallKit. V níže uvedeném příkladu, protože MonkeyCall přijímá CallKit, se systém nazývá stejným způsobem jako integrované systémové služby a získá všechny stejné funkce:In the example given below, since MonkeyCall adopts CallKit, it is known to the System in the same way as these built-in System Services and gets all of the same features:

Zásobník služby CallKitThe CallKit Service Stack

Podívejte se blíže na aplikaci MonkeyCall z diagramu výše.Take a closer look at the MonkeyCall App from the diagram above. Aplikace obsahuje veškerý kód ke komunikaci s vlastní sítí a obsahuje vlastní uživatelská rozhraní.The app contains all of its code to communicate with its own network and contains its own User Interfaces. Odkazy v CallKit ke komunikaci se systémem:It links in CallKit to communicate with the system:

Architektura aplikace MonkeyCallMonkeyCall App Architecture

V CallKit existují dvě hlavní rozhraní, která aplikace používá:There are two main interfaces in CallKit that the app uses:

  • CXProvider – Díky tomu může aplikace MonkeyCall informovat systém o všech neintegrovaných oznámeních, která by mohla nastat.CXProvider - This allows the MonkeyCall app to inform the system of any out-of-band notifications that might occur.
  • CXCallController – Povolí aplikaci MonkeyCall informovat systém o akcích místního uživatele.CXCallController - Allows the MonkeyCall app to inform the system of local user actions.

CXProviderThe CXProvider

Jak je uvedeno výše, CXProvider umožňuje aplikaci, aby informovala o všech neintegrovaných oznámeních, která se mohou vyskytnout.As stated above, CXProvider allows an app to inform the system of any out-of-band notifications that might occur. Jedná se o oznámení, která se nevyskytují v důsledku akcí místního uživatele, ale vyskytují se v důsledku externích událostí, jako jsou například příchozí hovory.These are notification that do not occur due to local user actions, but occur due to external events such as incoming calls.

Aplikace by měla použít CXProvider pro následující:An app should use the CXProvider for the following:

  • Nahlaste příchozí volání systému.Report an incoming call to the System.
  • Oznamte toto odchozí volání připojené k systému.Report an that outgoing call has connected to the System.
  • Nahlásit vzdáleného uživatele, který ukončuje volání do systému.Report the remote user ending the call to the System.

Když aplikace chce komunikovat se systémem, používá CXCallUpdate třídu a v případě, že systém potřebuje komunikovat s aplikací, používá CXAction třídu:When the app wants to communicate to the system, it uses the CXCallUpdate class and when the System needs to communicate with the app, it uses the CXAction class:

Komunikace se systémem přes CXProviderCommunicating with the system via a CXProvider

CXCallControllerThe CXCallController

CXCallControllerUmožňuje aplikaci informovat systém o akcích místního uživatele, jako je například uživatel, který spouští volání VoIP.The CXCallController allows an app to inform the system of local user actions such as the user starting a VOIP call. Implementací CXCallController aplikace se souhře s jinými typy volání v systému.By implementing a CXCallController the app gets to interplay with other types of calls in the system. Například pokud již probíhá aktivní telefonní hovor, CXCallController může aplikace VoIP umístit toto volání do pozastaveného a spustit nebo odpovědět na volání VoIP.For example, if there is already an active telephony call in progress, CXCallController can allow the VOIP app to place that call on hold and start or answer a VOIP call.

Aplikace by měla použít CXCallController pro následující:An app should use the CXCallController for the following:

  • Ohlásit, pokud uživatel zahájil odchozí volání do systému.Report when the user has started an outgoing call to the System.
  • Ohlásit, když uživatel odpoví na příchozí volání do systému.Report when the user answers an incoming call to the System.
  • Ohlásit, kdy uživatel ukončí volání do systému.Report when the user ends a call to the System.

Když aplikace chce sdělit systému akce místního uživatele, používá tuto CXTransaction třídu:When the app wants to communicate local user actions to the system, it uses the CXTransaction class:

Vytváření sestav do systému pomocí CXCallControllerReporting to the system using a CXCallController

Implementace CallKitImplementing CallKit

V následujících částech se dozvíte, jak implementovat CallKit v aplikaci pro Xamarin. iOS VOIP.The following sections will show how to implement CallKit in a Xamarin.iOS VOIP app. V takovém případě bude tento dokument používat kód z fiktivní aplikace VOIP MonkeyCall.For the sake of example, this document will be using code from the fictitious MonkeyCall VOIP app. Kód, který je uveden zde, představuje několik pomocných tříd, konkrétní části CallKit jsou podrobně popsány v následujících oddílech.The code presented here represents several supporting classes, the CallKit specific parts will covered in detail in the following sections.

Třída ActiveCallThe ActiveCall class

Tuto ActiveCall třídu používá aplikace MonkeyCall k uchovávání všech informací o volání VoIP, které je aktuálně aktivní následujícím způsobem:The ActiveCall class is used by the MonkeyCall app to hold all of the information about a VOIP call that is currently active as follows:

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í.ActiveCall holds several properties that define the state of the call and two events that can be raised when the call state changes. Vzhledem k tomu, že toto je pouze příklad, existují tři metody pro simulaci spuštění, zodpovězení a ukončení volání.Since this is an example only, there are three methods used to simulated starting, answering and ending a call.

Třída StartCallRequestThe StartCallRequest class

StartCallRequestStatická třída poskytuje několik pomocných metod, které se použijí při práci s odchozími voláními:The StartCallRequest static class, provides a few helper methods that will be used when working with outgoing calls:

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;
            }
        }
    }
}

CallHandleFromURLTřídy a CallHandleFromActivity se používají v AppDelegate k získání popisovače kontaktu osoby, která je volána při odchozím volání.The CallHandleFromURL and CallHandleFromActivity classes are used in the AppDelegate to get the contact handle of the person being called in an outgoing call. Další informace najdete níže v části zpracování odchozích hovorů .For more information, please see the Handling Outgoing Calls section below.

Třída ActiveCallManagerThe ActiveCallManager class

ActiveCallManagerTřída zpracovává všechna otevřená volání v aplikaci MonkeyCall.The ActiveCallManager class handles all open calls in the MonkeyCall app.

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 toto je pouze simulace, ActiveCallManager udržuje pouze kolekci ActiveCall objektů a má rutinu pro nalezení daného volání UUID vlastností.Again, since this is a simulation only, the ActiveCallManager only maintains a collection of ActiveCall objects and has a routine for finding a given call by its UUID property. Zahrnuje také metody pro spuštění, ukončení a změnu stavu zablokování odchozího volání.It also includes methods to start, end and change the on-hold state of an outgoing call. Další informace najdete níže v části zpracování odchozích hovorů .For more information, please see the Handling Outgoing Calls section below.

Třída ProviderDelegateThe ProviderDelegate class

Jak je popsáno výše, CXProvider poskytuje obousměrnou komunikaci mezi aplikací a systémem pro neplánovaná oznámení.As discussed above, a CXProvider provides two-way communication between the app and the System for out-of-band notifications. Vývojář musí zadat vlastní CXProviderDelegate a připojit ho k CXProvider aplikaci, aby mohla zpracovávat události CallKit mimo IP síť.The developer needs to provide a custom CXProviderDelegate and attach it to the CXProvider for the app to handle out-of-band CallKit events. MonkeyCall používá následující CXProviderDelegate :MonkeyCall uses the following CXProviderDelegate:

using System;
using Foundation;
using CallKit;
using UIKit;

namespace MonkeyCall
{
    public class ProviderDelegate : CXProviderDelegate
    {
        #region Computed Properties
        public ActiveCallManager CallManager { get; set;}
        public CXProviderConfiguration Configuration { get; set; }
        public CXProvider Provider { get; set; }
        #endregion

        #region Constructors
        public ProviderDelegate (ActiveCallManager callManager)
        {
            // Save connection to call manager
            CallManager = callManager;

            // Define handle types
            var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };

            // Get Image Template
            var templateImage = UIImage.FromFile ("telephone_receiver.png");

            // Setup the initial configurations
            Configuration = new CXProviderConfiguration ("MonkeyCall") {
                MaximumCallsPerCallGroup = 1,
                SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
                IconTemplateImageData = templateImage.AsPNG(),
                RingtoneSound = "musicloop01.wav"
            };

            // Create a new provider
            Provider = new CXProvider (Configuration);

            // Attach this delegate
            Provider.SetDelegate (this, null);

        }
        #endregion

        #region Override Methods
        public override void DidReset (CXProvider provider)
        {
            // Remove all calls
            CallManager.Calls.Clear ();
        }

        public override void PerformStartCallAction (CXProvider provider, CXStartCallAction action)
        {
            // Create new call record
            var activeCall = new ActiveCall (action.CallUuid, action.CallHandle.Value, true);

            // Monitor state changes
            activeCall.StartingConnectionChanged += (call) => {
                if (call.isConnecting) {
                    // Inform system that the call is starting
                    Provider.ReportConnectingOutgoingCall (call.UUID, call.StartedConnectingOn.ToNSDate());
                }
            };

            activeCall.ConnectedChanged += (call) => {
                if (call.isConnected) {
                    // Inform system that the call has connected
                    provider.ReportConnectedOutgoingCall (call.UUID, call.ConnectedOn.ToNSDate ());
                }
            };

            // Start call
            activeCall.StartCall ((successful) => {
                // Was the call able to be started?
                if (successful) {
                    // Yes, inform the system
                    action.Fulfill ();

                    // Add call to manager
                    CallManager.Calls.Add (activeCall);
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformAnswerCallAction (CXProvider provider, CXAnswerCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Attempt to answer call
            call.AnswerCall ((successful) => {
                // Was the call successfully answered?
                if (successful) {
                    // Yes, inform system
                    action.Fulfill ();
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformEndCallAction (CXProvider provider, CXEndCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Attempt to answer call
            call.EndCall ((successful) => {
                // Was the call successfully answered?
                if (successful) {
                    // Remove call from manager's queue
                    CallManager.Calls.Remove (call);

                    // Yes, inform system
                    action.Fulfill ();
                } else {
                    // No, inform system
                    action.Fail ();
                }
            });
        }

        public override void PerformSetHeldCallAction (CXProvider provider, CXSetHeldCallAction action)
        {
            // Find requested call
            var call = CallManager.FindCall (action.CallUuid);

            // Found?
            if (call == null) {
                // No, inform system and exit
                action.Fail ();
                return;
            }

            // Update hold status
            call.isOnHold = action.OnHold;

            // Inform system of success
            action.Fulfill ();
        }

        public override void TimedOutPerformingAction (CXProvider provider, CXAction action)
        {
            // Inform user that the action has timed out
        }

        public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
        {
            // Start the calls audio session here
        }

        public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
        {
            // End the calls audio session and restart any non-call
            // related audio
        }
        #endregion

        #region Public Methods
        public void ReportIncomingCall (NSUuid uuid, string handle)
        {
            // Create update to describe the incoming call and caller
            var update = new CXCallUpdate ();
            update.RemoteHandle = new CXHandle (CXHandleType.Generic, handle);

            // Report incoming call to system
            Provider.ReportNewIncomingCall (uuid, update, (error) => {
                // Was the call accepted
                if (error == null) {
                    // Yes, report to call manager
                    CallManager.Calls.Add (new ActiveCall (uuid, handle, false));
                } else {
                    // Report error to user here
                    Console.WriteLine ("Error: {0}", error);
                }
            });
        }
        #endregion
    }
}

Když je vytvořena instance tohoto delegáta, je předána ActiveCallManager , že se bude používat pro zpracování jakékoli aktivity volání.When an instance of this delegate is created, it's passed the ActiveCallManager that it will use to handle any call activity. Dále definuje typy popisovačů ( CXHandleType ), CXProvider na které bude reagovat:Next, it defines the handle types (CXHandleType) that the CXProvider will respond to:

// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };

A získá image šablony, která se použije na ikonu aplikace v případě, že probíhá volání:And it gets the template image that will be applied to the app's icon when a call is in progress:

// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");

Tyto hodnoty se nastavují do sady CXProviderConfiguration , které se použijí ke konfiguraci CXProvider :These values get bundled into a CXProviderConfiguration that will be used to configure the CXProvider:

// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
    MaximumCallsPerCallGroup = 1,
    SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
    IconTemplateImageData = templateImage.AsPNG(),
    RingtoneSound = "musicloop01.wav"
};

Delegát potom vytvoří nový CXProvider s těmito konfiguracemi a připojí k němu vlastní:The delegate then creates a new CXProvider with these configurations and attaches itself to it:

// Create a new provider
Provider = new CXProvider (Configuration);

// Attach this delegate
Provider.SetDelegate (this, null);

Když použijete CallKit, aplikace už nebude vytvářet a zpracovávat vlastní zvukové relace, místo toho bude muset konfigurovat a používat zvukovou relaci, kterou bude systém pro něj vytvářet a zpracovávat.When using CallKit, the app will no longer create and handle its own audio sessions, instead it will need to configure and use an audio session that the System will create and handle for it.

Pokud se jednalo o skutečnou aplikaci, DidActivateAudioSession Metoda by se použila k zahájení volání s předem nakonfigurovaným AVAudioSession systémem, který je k dispozici:If this were a real app, the DidActivateAudioSession method would be used to start the call with a pre-configured AVAudioSession that the System provided:

public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
    // Start the call's audio session here...
}

K DidDeactivateAudioSession finalizaci a uvolnění připojení k zadané zvukové relaci v systému by se použila také metoda:It would also use the DidDeactivateAudioSession method to finalize and release its connection to the System provided audio session:

public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
    // End the calls audio session and restart any non-call
    // releated audio
}

Zbytek kódu se podrobně popisuje v níže uvedených částech.The rest of the code will be covered in detail in the sections that follow.

Třída AppDelegateThe AppDelegate class

MonkeyCall používá AppDelegate k ukládání instancí ActiveCallManager a CXProviderDelegate , které se použijí v celé aplikaci:MonkeyCall uses the AppDelegate to hold instances of the ActiveCallManager and CXProviderDelegate that will be used throughout the app:

using Foundation;
using UIKit;
using Intents;
using System;

namespace MonkeyCall
{
    [Register ("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        #region Constructors
        public override UIWindow Window { get; set; }
        public ActiveCallManager CallManager { get; set; }
        public ProviderDelegate CallProviderDelegate { get; set; }
        #endregion

        #region Override Methods
        public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
        {
            // Initialize the call handlers
            CallManager = new ActiveCallManager ();
            CallProviderDelegate = new ProviderDelegate (CallManager);

            return true;
        }

        public override bool OpenUrl (UIApplication app, NSUrl url, NSDictionary options)
        {
            // Get handle from url
            var handle = StartCallRequest.CallHandleFromURL (url);

            // Found?
            if (handle == null) {
                // No, report to system
                Console.WriteLine ("Unable to get call handle from URL: {0}", url);
                return false;
            } else {
                // Yes, start call and inform system
                CallManager.StartCall (handle);
                return true;
            }
        }

        public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
        {
            var handle = StartCallRequest.CallHandleFromActivity (userActivity);

            // Found?
            if (handle == null) {
                // No, report to system
                Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
                return false;
            } else {
                // Yes, start call and inform system
                CallManager.StartCall (handle);
                return true;
            }
        }

        ...
        #endregion
    }
}

OpenUrlMetody a ContinueUserActivity přepsání se používají, když aplikace zpracovává odchozí volání.The OpenUrl and ContinueUserActivity override methods are used when the app is processing an outgoing call. Další informace najdete níže v části zpracování odchozích hovorů .For more information, please see the Handling Outgoing Calls section below.

Zpracování příchozích voláníHandling incoming calls

Existuje několik stavů a procesů, které může příchozí volání VOIP projít během typického pracovního postupu příchozího volání, jako je:There are several states and processes that an incoming VOIP call can go through during a typical incoming call workflow such as:

  • Informování uživatele (a systému), že příchozí volání existuje.Informing the user (and the System) that an incoming call exists.
  • Přijímání oznámení, když chce uživatel odpovědět na volání a inicializovat volání u druhého uživatele.Receiving notification when the user wants to answer the call and initializing the call with the other user.
  • Informujte systém a komunikační síť, když chce uživatel ukončit aktuální hovor.Inform the System and the Communication Network when the user wants to end the current call.

V následujících částech se podíváme na to, jak může aplikace používat CallKit ke zpracování pracovního postupu příchozího volání, a to znovu pomocí aplikace MonkeyCall VOIP jako příkladu.The following sections will take a detailed look at how an app can use CallKit to handle the incoming call workflow, again using the MonkeyCall VOIP app as an example.

Informování uživatele o příchozím voláníInforming user of incoming call

Když vzdálený uživatel spustí konverzaci VOIP s místním uživatelem, dojde k následujícímu:When a remote user has started a VOIP conversation with the local user, the following occurs:

Vzdálený uživatel spustil konverzaci VOIP.A remote user has started a VOIP conversation

  1. Aplikace získá oznámení ze své komunikační sítě, že existuje příchozí volání VOIP.The app gets a notification from its Communications Network that there is an incoming VOIP call.
  2. Aplikace používá CXProvider k odeslání do CXCallUpdate systému systém, který bude informovat o volání.The app uses the CXProvider to send a CXCallUpdate to the System informing it of the call.
  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.The System publishes the call to the System UI, System Services and any other VOIP apps using CallKit.

Například v CXProviderDelegate :For example, in the 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.This code creates a new CXCallUpdate instance and attaches a handle to it that will identify the caller. Dále používá ReportNewIncomingCall metodu CXProvider třídy k informování systému o volání.Next, it uses the ReportNewIncomingCall method of the CXProvider class to inform the system of the call. 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.If it is successful, the call is added to the app's collection of active calls, if it isn't, the error needs to be reported to the user.

Příchozí volání uživatele do zodpovězeníUser answering incoming call

Pokud uživatel chce odpovědět na příchozí volání VOIP, dojde k následujícímu:If the user wants to answer the incoming VOIP call, the following occurs:

Uživatel odpoví příchozí volání VOIP.The user answers the incoming VOIP call

  1. Systémové uživatelské rozhraní informuje systém o tom, že uživatel chce odpovědět na volání VOIP.The System UI informs the System that the user wants to answer the VOIP call.
  2. Systém pošle CXAnswerCallAction aplikaci, která bude informovat o CXProvider záměru odpovědi.The System sends a CXAnswerCallAction to the app's CXProvider informing it of the Answer Intent.
  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.The app informs its Communication Network that the user is answering the call and the VOIP call proceeds as usual.

Například v CXProviderDelegate :For example, in the 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í.This code first searches for the given call in its list of active calls. Pokud se volání nenajde, systém se oznámí a metoda se ukončí.If the call can't be found, the system is notified and the method exits. 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.If it is found, the AnswerCall method of the ActiveCall class is called to start the call and the System is information if it succeeds or fails.

Uživatel ukončuje příchozí volání.User ending incoming call

Pokud si uživatel přeje ukončit volání v uživatelském rozhraní aplikace, dojde k následujícímu:If the user wishes to terminate the call from within the app's UI, the following occurs:

Uživatel ukončí volání v uživatelském rozhraní aplikace.The user terminates the call from within the app's UI

  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čí.The app creates CXEndCallAction that gets bundled into a CXTransaction that is sent to the System to inform it that the call is ending.
  2. Systém ověří záměr koncového volání a pošle CXEndCallAction zpátky do aplikace prostřednictvím CXProvider .The System verifies the End Call Intent and sends the CXEndCallAction back to the app via the CXProvider.
  3. Aplikace poté informuje svou komunikační síť o ukončení volání.The app then informs its Communication Network that the call is ending.

Například v CXProviderDelegate :For example, in the 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í.This code first searches for the given call in its list of active calls. Pokud se volání nenajde, systém se oznámí a metoda se ukončí.If the call can't be found, the system is notified and the method exits. 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.If it is found, the EndCall method of the ActiveCall class is called to end the call and the System is information if it succeeds or fails. Je-li to úspěšné, volání je odebráno z kolekce aktivních volání.If successful, the call is removed from the collection of active calls.

Správa více voláníManaging multiple calls

Většina aplikací VOIP může zpracovávat více volání najednou.Most VOIP apps can handle multiple calls at once. 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.For example, if there is currently an active VOIP call and the app gets notification that a there is a new incoming call, the user can pause or hang-up on the first call to answer the second one.

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 ).In the situation give above, the System will send a CXTransaction to the app that will include a list of multiple actions (such as the CXEndCallAction and the CXAnswerCallAction). Všechny tyto akce bude nutné splnit jednotlivě, aby systém mohl odpovídajícím způsobem aktualizovat uživatelské rozhraní.All of these actions will need to be fulfilled individually, so that the System can update the UI appropriately.

Zpracování odchozích voláníHandling outgoing calls

Pokud uživatel klepne na položku ze seznamu posledních položek (v aplikaci pro 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í :If the user taps an entry from the Recents list (in the Phone app), for example, that is from a call belonging to the app, it will be sent a Start Call Intent by the system:

Přijetí záměru spustit voláníReceiving a Start Call Intent

  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.The app will create a Start Call Action based on the Start Call Intent that it received from the System.
  2. Aplikace bude používat CXCallController k žádosti o akci spustit hovor ze systému.The app will use the CXCallController to request the Start Call Action from the system.
  3. Pokud systém přijme akci, bude vrácena do aplikace prostřednictvím XCProvider delegáta.If the System accepts the Action, it will be returned to the app via the XCProvider delegate.
  4. Aplikace spustí odchozí volání v rámci komunikační sítě.The app starts the outgoing call with its Communication Network.

Další informace o záměrech najdete v dokumentaci k našim záměrům a rozšířením uživatelského rozhraní .For more information on Intents, please see our Intents and Intents UI Extensions documentation.

Životní cyklus odchozího voláníThe outgoing call lifecycle

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:When working with CallKit and an outgoing call, the app will need to inform the System of the following lifecycle events:

  1. Od začátku – informujte systém, že se chystá spuštění odchozího volání.Starting - Inform the system that an outgoing call is about to start.
  2. Začalo – informujte systém, zda bylo zahájeno odchozí volání.Started - Inform the system that an outgoing call has started.
  3. Připojování – informujte systém, že odchozí volání se připojuje.Connecting - Inform the system that the outgoing call is connecting.
  4. Připojeno – informujte, že odchozí volání se připojilo a že obě strany si můžou mluvit hned.Connected - Inform the outgoing call has connected and that both parties can talk now.

Například následující kód spustí odchozí volání:For example, the following code will start an outgoing call:

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.It creates a CXHandle and uses it to configure a CXStartCallAction which is bundled into a CXTransaction that is sent to the System using the RequestTransaction method of the CXCallController class. Když zavoláte RequestTransaction metodu, systém může umístit jakákoli existující volání za běhu, bez ohledu na zdroj (Phone app, FaceTime, VoIP atd.) před zahájením nového volání.By calling the RequestTransaction method, the System can place any existing calls on-hold, no matter the source (Phone app, FaceTime, VOIP, etc.), before the new call starts.

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 naposledy použitých položek (v aplikaci Phone).The request to start an outgoing VOIP call can come from several different sources, such as Siri, an entry on a Contact card (in the Contacts app) or from the Recents list (in the Phone app). 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:In these situations, the app will be sent a Start Call Intent inside a NSUserActivity and the AppDelegate will need to handle it:

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 Třída StartCallRequest výše).Here the CallHandleFromActivity method of the helper class StartCallRequest is being used to get the handle to the person being called (see The StartCallRequest Class above).

PerformStartCallActionMetoda třídy ProviderDelegate slouží ke konečnému spuštění samotného odchozího hovoru a informování o jeho životním cyklu:The PerformStartCallAction method of the ProviderDelegate Class is used to finally start the actual outgoing call and inform the System of its lifecycle:

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.It creates an instance of the ActiveCall class (to hold information about the call in progress) and populates with the person being called. StartingConnectionChangedUdálosti a ConnectedChanged se používají k monitorování a hlášení životního cyklu odchozího volání.The StartingConnectionChanged and ConnectedChanged events are used to monitor and report the outgoing call lifecycle. Volání je zahájeno a systém informoval, že akce byla splněna.The call is started and the System informed that the Action was fulfilled.

Ukončení odchozího voláníEnding an outgoing call

Až se uživatel dokončí odchozím voláním a chce ho ukončit, můžete použít následující kód:When the user has finished with an outgoing call and wishes to end it, the following code can be used:

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.If creates a CXEndCallAction with the UUID of the call to end, bundles it in a CXTransaction that is sent to the System using the RequestTransaction method of the CXCallController class.

Další podrobnosti CallKitAdditional CallKit details

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:This section will cover some additional details that the developer will need to take into consideration when working with CallKit such as:

  • Konfigurace zprostředkovateleProvider Configuration
  • Chyby akceAction Errors
  • Systémová omezeníSystem Restrictions
  • Zvuk VOIPVOIP Audio

Konfigurace zprostředkovateleProvider configuration

Konfigurace zprostředkovatele umožňuje aplikaci VOIP s iOS 10 přizpůsobit uživatelské prostředí (uvnitř nativního uživatelského rozhraní v volání) při práci s CallKit.The provider configuration allows an iOS 10 VOIP app to customize the user experience (inside the native In-Call UI) when working with CallKit.

Aplikace může provádět následující typy přizpůsobení:An app can make the following types of customizations:

  • Zobrazit lokalizovaný název.Display a localized name.
  • Povolte podporu pro audiovizuální volání.Enable video call support.
  • Přizpůsobte tlačítka v uživatelském rozhraní volání, a to tak, že prezentujete vlastní ikonu obrázku šablony.Customize the buttons on the In-Call UI by presenting its own template image icon. Interakce uživatele s vlastními tlačítky se odesílá přímo do aplikace ke zpracování.User interaction with custom buttons is sent directly to the app to be processed.

Chyby akceAction errors

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.iOS 10 VOIP apps using CallKit need to handle Actions failing gracefully and keep the user informed of the Action state at all times.

Vezměte v úvahu následující příklad:Take the following example into consideration:

  1. Aplikace přijala akci spustit hovor a začala Postup inicializace nového volání VOIP v rámci komunikační sítě.The app has received a Start Call Action and has begun the process of initializing a new VOIP call with its Communication Network.
  2. Kvůli omezené nebo žádné síťové komunikační možnosti se připojení nezdařilo.Because of limited or no network communication capability, this connection fails.
  3. Aplikace musí odeslat zprávu o selhání zpět do akce zahájit volání ( Action.Fail() ), aby informovala systém o selhání.The app must send the Fail message back to the Start Call Action (Action.Fail()) to inform the System of the failure.
  4. To umožňuje systému informovat uživatele o stavu volání.This allows the System to inform the user of the status of the call. Například pro zobrazení uživatelského rozhraní selhání volání.For example, to display the Call Failure UI.

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.Additionally, an iOS 10 VOIP app will need to respond to Timeout Errors that can occur when an expected Action cannot be processed within a given amount of time. Každý typ akce poskytovaný CallKit má přiřazenou maximální hodnotu časového limitu.Each Action Type provided by CallKit has a maximum Timeout value associated with it. 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.These Timeout values ensure that any CallKit Action requested by the user is handled in a responsive fashion, thus keeping the OS fluid and responsive as well.

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.There are several methods on the Provider Delegate (CXProviderDelegate) that should be overridden to gracefully handle this Timeout situations as well.

Systémová omezeníSystem restrictions

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í.Based on the current state of the iOS device running the iOS 10 VOIP app, certain system restrictions may be enforced.

Například příchozí volání VOIP může být omezeno systémem, pokud:For example, an incoming VOIP call can be restricted by the System if:

  1. Volající uživatel se nachází v seznamu blokovaných volajících uživatele.The person calling is on the user's Blocked Caller List.
  2. Zařízení iOS uživatele je v režimu Nerušit.The user's iOS device is in the Do-Not-Disturb mode.

Pokud je volání VOIP omezeno některou z těchto situací, použijte následující kód k jeho zpracování:If a VOIP call is restricted by any of these situations, use the following code to handle it:

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 VOIPVOIP audio

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.CallKit provides several benefits for handling the audio resources that an iOS 10 VOIP app will require during a live VOIP call. Jednou z největších výhod je, že zvuková relace aplikace bude mít vyšší prioritu při provozu v iOS 10.One of the biggest benefits is the app's audio session will have elevated priorities when running in iOS 10. Jedná se o stejnou úroveň priority, jakou mají předdefinované aplikace pro 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.This is the same priority level as the built in Phone and FaceTime apps and this enhanced priority level will prevent other running apps from interrupting the VOIP app's audio session.

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í.Additionally, CallKit has access to other audio routing hints that can enhance the performance and intelligently route VOIP audio to specific output devices during a live call based on user preferences and device states. 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í.For example, based on attached devices such as bluetooth headphones, a live CarPlay connection or Accessibility settings.

Během životního cyklu typického volání VOIP pomocí CallKit bude aplikace muset nakonfigurovat zvukový stream, který CallKit poskytne.During the lifecycle of a typical VOIP call using CallKit, the app will need to configure the Audio Stream that CallKit will provide it. Podívejte se na následující příklad:Take a look at the following example:

Sekvence akcí spustit voláníThe Start Call Action Sequence

  1. Aplikace přijme akci spustit volání, aby odpovídala příchozímu volání.A Start Call Action is received by the app to answer an incoming call.
  2. Než tuto akci splní aplikace, poskytne konfiguraci, která bude pro svůj požadavek vyžadovat AVAudioSession .Before this Action is fulfilled by the app, it provides the configuration that is will require for its AVAudioSession.
  3. Aplikace informuje systém o tom, že akce byla splněna.The app informs the System that the Action has been fulfilled.
  4. Před připojením volání CallKit poskytuje vysokou prioritu AVAudioSession odpovídající konfiguraci, kterou aplikace požaduje.Before the call connects, CallKit provides a high-priority AVAudioSession matching the configuration that the app requested. Aplikace bude oznámena prostřednictvím DidActivateAudioSession metody CXProviderDelegate .The app will be notified via the DidActivateAudioSession method of its CXProviderDelegate.

Práce s rozšířeními adresáře voláníWorking with call directory extensions

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.When working with CallKit, Call Directory Extensions provide a way to add blocked call numbers and identify numbers that are specific to a given VOIP app to contacts in the Contact app on the iOS device.

Implementace rozšíření adresáře voláníImplementing a Call directory extension

Pokud chcete implementovat rozšíření adresáře volání v aplikaci Xamarin. iOS, udělejte toto:To implement a Call Directory Extension in a Xamarin.iOS app, do the following:

  1. Otevřete řešení aplikace v Visual Studio pro Mac.Open the app's solution in Visual Studio for Mac.

  2. V Průzkumník řešení klikněte pravým tlačítkem myši na název řešení a vyberte Přidat > Přidat nový projekt.Right-click on the Solution Name in the Solution Explorer and select Add > Add New Project.

  3. Vyberte rozšíření pro iOS > rozšíření > adresáře volání a klikněte na tlačítko Další :Select iOS > Extensions > Call Directory Extensions and click the Next button:

    Vytváří se nové rozšíření adresáře volání.Creating a new Call Directory Extension

  4. Zadejte název rozšíření a klikněte na tlačítko Další :Enter a Name for the extension and click the Next button:

    Zadání názvu pro rozšířeníEntering a name for the extension

  5. V případě potřeby upravte název projektu nebo název řešení a klikněte na tlačítko vytvořit :Adjust the Project Name and/or Solution Name if required and click the Create button:

    Vytváření projektuCreating the project

Tím se CallDirectoryHandler.cs do projektu přidá třída, která vypadá následovně:This will add a CallDirectoryHandler.cs class to the project that looks like the following:

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.The BeginRequest method in the Call Directory Handler will need to be modified to provide the required functionality. 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.In the case of the sample above, it attempts to set the list of blocked and available numbers in the VOIP app's contacts database. 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.If either requests fails for any reason, create an NSError to describe the failure and pass it the CancelRequest method of the CXCallDirectoryExtensionContext class.

Pro nastavení blokovaných čísel použijte AddBlockingEntry metodu CXCallDirectoryExtensionContext třídy.To set the blocked numbers use the AddBlockingEntry method of the CXCallDirectoryExtensionContext class. Čísla poskytnutá metodě musí být v číselném vzestupném pořadí.The numbers provided to the method must be in numerically ascending order. 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.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.

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.To inform to Contact app of the contact numbers known to the VOIP app, use the AddIdentificationEntry method of the CXCallDirectoryExtensionContext class and provide both the number and an identifying label. Čísla poskytnutá metodě musí být v číselném vzestupném pořadí.Again, the numbers provided to the method must be in numerically ascending order. 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.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.

SouhrnSummary

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.This article has covered the new CallKit API that Apple released in iOS 10 and how to implement it in Xamarin.iOS VOIP apps. Ukazují, jak CallKit umožňuje integraci aplikace do systému iOS, jak poskytuje paritu funkcí s integrovanými aplikacemi (jako je třeba 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ů.It has shown how CallKit allows an app to integrate into the iOS System, how it provides feature parity with built-in apps (such as Phone) and how it increases an app's visibility throughout iOS in locations such as the Lock and Home Screens, via Siri interactions and via the Contacts apps.