CallKit di Xamarin.iOS
Api CallKit baru di iOS 10 menyediakan cara bagi aplikasi VOIP untuk berintegrasi dengan I Telepon UI dan memberikan antarmuka dan pengalaman yang familier kepada pengguna akhir. Dengan api ini pengguna dapat melihat dan berinteraksi dengan panggilan VOIP dari Layar Kunci perangkat iOS dan mengelola kontak menggunakan tampilan Favorit dan Terbaru aplikasi Telepon.
Tentang CallKit
Menurut Apple, CallKit adalah kerangka kerja baru yang akan meningkatkan aplikasi Voice Over IP (VOIP) pihak ke-3 ke pengalaman pihak ke-1 di iOS 10. Api CallKit memungkinkan aplikasi VOIP untuk berintegrasi dengan I Telepon UI dan memberikan antarmuka dan pengalaman yang familier kepada pengguna akhir. Sama seperti aplikasi Telepon bawaan, pengguna dapat melihat dan berinteraksi dengan panggilan VOIP dari Layar Kunci perangkat iOS dan mengelola kontak menggunakan tampilan Favorit dan Terbaru aplikasi Telepon.
Selain itu, API CallKit menyediakan kemampuan untuk membuat Ekstensi Aplikasi yang dapat mengaitkan nomor telepon dengan nama (ID Pemanggil) atau memberi tahu sistem kapan nomor harus diblokir (Pemblokiran Panggilan).
Pengalaman aplikasi VOIP yang ada
Sebelum membahas Api CallKit baru dan kemampuannya, lihat pengalaman pengguna saat ini dengan aplikasi VOIP pihak ke-3 di iOS 9 (dan lebih sedikit) menggunakan aplikasi VOIP fiktif bernama MonkeyCall. MonkeyCall adalah aplikasi sederhana yang memungkinkan pengguna mengirim dan menerima panggilan VOIP menggunakan API iOS yang ada.
Saat ini, jika pengguna menerima panggilan masuk di MonkeyCall dan i mereka Telepon dikunci, pemberitahuan yang diterima di layar Kunci tidak dapat dibedakan dari jenis pemberitahuan lain (seperti yang berasal dari aplikasi Pesan atau Email misalnya).
Jika pengguna ingin menjawab panggilan, mereka harus menggeser pemberitahuan MonkeyCall untuk membuka aplikasi dan memasukkan kode sandi mereka (atau ID Sentuh pengguna) untuk membuka kunci telepon sebelum mereka dapat menerima panggilan dan memulai percakapan.
Pengalaman ini sama rumitnya jika ponsel tidak terkunci. Sekali lagi, panggilan MonkeyCall masuk ditampilkan sebagai banner pemberitahuan standar yang meluncur masuk dari bagian atas layar. Karena pemberitahuan bersifat sementara, dapat dengan mudah dilewatkan oleh pengguna yang memaksa mereka untuk membuka Pusat Pemberitahuan dan menemukan pemberitahuan khusus untuk menjawab kemudian memanggil atau menemukan dan meluncurkan aplikasi MonkeyCall secara manual.
Pengalaman aplikasi CallKit VOIP
Dengan menerapkan API CallKit baru di aplikasi MonkeyCall, pengalaman pengguna dengan panggilan VOIP masuk dapat sangat ditingkatkan di iOS 10. Ambil contoh pengguna yang menerima panggilan VOIP saat ponsel mereka dikunci dari atas. Dengan menerapkan CallKit, panggilan akan muncul di layar Kunci i Telepon, seperti halnya jika panggilan diterima dari aplikasi Telepon bawaan, dengan UI layar penuh, asli, dan fungsionalitas gesek-ke-jawaban standar.
Sekali lagi, jika i Telepon tidak terkunci saat panggilan MONKEYCall VOIP diterima, antarmuka pengguna asli layar penuh yang sama, dan fungsionalitas geser ke jawaban standar dan ketuk untuk menolak aplikasi Telepon bawaan disajikan dan MonkeyCall memiliki opsi untuk memutar nada dering kustom.
CallKit menyediakan fungsionalitas tambahan untuk MonkeyCall, memungkinkan panggilan VOIP-nya berinteraksi dengan jenis panggilan lain, untuk muncul di daftar Terbaru dan Favorit bawaan, untuk menggunakan fitur Jangan Ganggu dan Blokir bawaan, mulai panggilan MonkeyCall dari Siri dan menawarkan kemampuan bagi pengguna untuk menetapkan panggilan MonkeyCall kepada orang-orang di aplikasi Kontak.
Bagian berikut akan mencakup arsitektur CallKit, alur panggilan masuk dan keluar, dan API CallKit secara rinci.
Arsitektur CallKit
Di iOS 10, Apple telah mengadopsi CallKit di semua Layanan Sistem sehingga panggilan yang dilakukan di CarPlay, misalnya, diketahui oleh UI Sistem melalui CallKit. Dalam contoh yang diberikan di bawah ini, karena MonkeyCall mengadopsi CallKit, itu dikenal oleh Sistem dengan cara yang sama seperti Layanan Sistem bawaan ini dan mendapatkan semua fitur yang sama:
Lihat lebih dekat Aplikasi MonkeyCall dari diagram di atas. Aplikasi ini berisi semua kodenya untuk berkomunikasi dengan jaringannya sendiri dan berisi Antarmuka Penggunanya sendiri. Ini menautkan di CallKit untuk berkomunikasi dengan sistem:
Ada dua antarmuka utama di CallKit yang digunakan aplikasi:
CXProvider
- Ini memungkinkan aplikasi MonkeyCall untuk menginformasikan sistem pemberitahuan di luar band yang mungkin terjadi.CXCallController
- Memungkinkan aplikasi MonkeyCall untuk menginformasikan sistem tindakan pengguna lokal.
The CXProvider
Seperti yang dinyatakan di atas, CXProvider
memungkinkan aplikasi untuk menginformasikan sistem pemberitahuan di luar band yang mungkin terjadi. Ini adalah pemberitahuan yang tidak terjadi karena tindakan pengguna lokal, tetapi terjadi karena peristiwa eksternal seperti panggilan masuk.
Aplikasi harus menggunakan CXProvider
untuk hal berikut:
- Laporkan panggilan masuk ke Sistem.
- Laporkan bahwa panggilan keluar telah tersambung ke Sistem.
- Laporkan pengguna jarak jauh yang mengakhiri panggilan ke Sistem.
Ketika aplikasi ingin berkomunikasi dengan sistem, aplikasi menggunakan CXCallUpdate
kelas dan ketika Sistem perlu berkomunikasi dengan aplikasi, aplikasi menggunakan CXAction
kelas :
The CXCallController
memungkinkan CXCallController
aplikasi untuk menginformasikan sistem tindakan pengguna lokal seperti pengguna yang memulai panggilan VOIP. Dengan mengimplementasikan CXCallController
aplikasi, aplikasi akan saling berhubungan dengan jenis panggilan lain dalam sistem. Misalnya, jika sudah ada panggilan telepon aktif yang sedang berlangsung, CXCallController
dapat memungkinkan aplikasi VOIP untuk melakukan panggilan tersebut ditangguhkan dan memulai atau menjawab panggilan VOIP.
Aplikasi harus menggunakan CXCallController
untuk hal berikut:
- Laporkan ketika pengguna telah memulai panggilan keluar ke Sistem.
- Laporkan saat pengguna menjawab panggilan masuk ke Sistem.
- Laporkan saat pengguna mengakhiri panggilan ke Sistem.
Ketika aplikasi ingin mengkomunikasikan tindakan pengguna lokal ke sistem, aplikasi menggunakan CXTransaction
kelas :
Menerapkan CallKit
Bagian berikut akan menunjukkan cara mengimplementasikan CallKit di aplikasi Xamarin.iOS VOIP. Demi contoh, dokumen ini akan menggunakan kode dari aplikasi MonkeyCall VOIP fiktif. Kode yang disajikan di sini mewakili beberapa kelas pendukung, bagian spesifik CallKit akan tercakup secara rinci di bagian berikut.
Kelas ActiveCall
Kelas ActiveCall
ini digunakan oleh aplikasi MonkeyCall untuk menyimpan semua informasi tentang panggilan VOIP yang saat ini aktif sebagai berikut:
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
menyimpan beberapa properti yang menentukan status panggilan dan dua peristiwa yang dapat dinaikkan saat status panggilan berubah. Karena ini adalah contoh saja, ada tiga metode yang digunakan untuk mensimulasikan memulai, menjawab, dan mengakhiri panggilan.
Kelas StartCallRequest
Kelas StartCallRequest
statis, menyediakan beberapa metode pembantu yang akan digunakan saat bekerja dengan panggilan keluar:
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;
}
}
}
}
Kelas CallHandleFromURL
dan CallHandleFromActivity
digunakan di AppDelegate untuk mendapatkan handel kontak orang yang dipanggil dalam panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.
Kelas ActiveCallManager
Kelas ActiveCallManager
menangani semua panggilan terbuka di aplikasi MonkeyCall.
using System;
using System.Collections.Generic;
using Foundation;
using CallKit;
namespace MonkeyCall
{
public class ActiveCallManager
{
#region Private Variables
private CXCallController CallController = new CXCallController ();
#endregion
#region Computed Properties
public List<ActiveCall> Calls { get; set; }
#endregion
#region Constructors
public ActiveCallManager ()
{
// Initialize
this.Calls = new List<ActiveCall> ();
}
#endregion
#region Private Methods
private void SendTransactionRequest (CXTransaction transaction)
{
// Send request to call controller
CallController.RequestTransaction (transaction, (error) => {
// Was there an error?
if (error == null) {
// No, report success
Console.WriteLine ("Transaction request sent successfully.");
} else {
// Yes, report error
Console.WriteLine ("Error requesting transaction: {0}", error);
}
});
}
#endregion
#region Public Methods
public ActiveCall FindCall (NSUuid uuid)
{
// Scan for requested call
foreach (ActiveCall call in Calls) {
if (call.UUID.Equals(uuid)) return call;
}
// Not found
return null;
}
public void StartCall (string contact)
{
// Build call action
var handle = new CXHandle (CXHandleType.Generic, contact);
var startCallAction = new CXStartCallAction (new NSUuid (), handle);
// Create transaction
var transaction = new CXTransaction (startCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void EndCall (ActiveCall call)
{
// Build action
var endCallAction = new CXEndCallAction (call.UUID);
// Create transaction
var transaction = new CXTransaction (endCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void PlaceCallOnHold (ActiveCall call)
{
// Build action
var holdCallAction = new CXSetHeldCallAction (call.UUID, true);
// Create transaction
var transaction = new CXTransaction (holdCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
public void RemoveCallFromOnHold (ActiveCall call)
{
// Build action
var holdCallAction = new CXSetHeldCallAction (call.UUID, false);
// Create transaction
var transaction = new CXTransaction (holdCallAction);
// Inform system of call request
SendTransactionRequest (transaction);
}
#endregion
}
}
Sekali lagi, karena ini hanya simulasi, satu-satunya ActiveCallManager
mempertahankan kumpulan ActiveCall
objek dan memiliki rutinitas untuk menemukan panggilan tertentu oleh propertinya UUID
. Ini juga mencakup metode untuk memulai, mengakhiri, dan mengubah status ditangguhkan dari panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.
Kelas ProviderDelegate
Seperti yang dibahas di atas, menyediakan CXProvider
komunikasi dua arah antara aplikasi dan Sistem untuk pemberitahuan di luar band. Pengembang perlu menyediakan kustom CXProviderDelegate
dan melampirkannya ke CXProvider
aplikasi untuk menangani peristiwa CallKit di luar band. MonkeyCall menggunakan yang berikut 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
}
}
Ketika instans delegasi ini dibuat, instans tersebut akan diteruskan ActiveCallManager
untuk menangani aktivitas panggilan apa pun. Selanjutnya, ini mendefinisikan jenis handel (CXHandleType
) yang CXProvider
akan merespons:
// Define handle types
var handleTypes = new [] { (NSNumber)(int)CXHandleType.PhoneNumber };
Dan mendapatkan gambar templat yang akan diterapkan ke ikon aplikasi saat panggilan sedang berlangsung:
// Get Image Template
var templateImage = UIImage.FromFile ("telephone_receiver.png");
Nilai-nilai ini dibundel ke dalam CXProviderConfiguration
yang akan digunakan untuk mengonfigurasi CXProvider
:
// Setup the initial configurations
Configuration = new CXProviderConfiguration ("MonkeyCall") {
MaximumCallsPerCallGroup = 1,
SupportedHandleTypes = new NSSet<NSNumber> (handleTypes),
IconTemplateImageData = templateImage.AsPNG(),
RingtoneSound = "musicloop01.wav"
};
Delegasi kemudian membuat baru CXProvider
dengan konfigurasi ini dan melampirkan dirinya sendiri ke dalamnya:
// Create a new provider
Provider = new CXProvider (Configuration);
// Attach this delegate
Provider.SetDelegate (this, null);
Saat menggunakan CallKit, aplikasi tidak akan lagi membuat dan menangani sesi audionya sendiri, sebaliknya perlu mengonfigurasi dan menggunakan sesi audio yang akan dibuat dan ditangani Sistem untuk itu.
Jika ini adalah aplikasi nyata, DidActivateAudioSession
metode ini akan digunakan untuk memulai panggilan dengan prakonfigurasi AVAudioSession
yang disediakan Sistem:
public override void DidActivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// Start the call's audio session here...
}
Ini juga akan menggunakan DidDeactivateAudioSession
metode untuk menyelesaikan dan merilis koneksinya ke sesi audio yang disediakan Sistem:
public override void DidDeactivateAudioSession (CXProvider provider, AVFoundation.AVAudioSession audioSession)
{
// End the calls audio session and restart any non-call
// releated audio
}
Sisa kode akan dibahas secara rinci di bagian berikut.
Kelas AppDelegate
MonkeyCall menggunakan AppDelegate untuk menyimpan instans ActiveCallManager
dan CXProviderDelegate
yang akan digunakan di seluruh aplikasi:
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
}
}
Metode OpenUrl
penimpaan dan ContinueUserActivity
digunakan saat aplikasi memproses panggilan keluar. Untuk informasi selengkapnya, silakan lihat bagian Menangani Panggilan Keluar di bawah ini.
Menangani panggilan masuk
Ada beberapa status dan proses yang dapat dilalui panggilan VOIP masuk selama alur kerja panggilan masuk umum seperti:
- Memberi tahu pengguna (dan Sistem) bahwa panggilan masuk ada.
- Menerima pemberitahuan ketika pengguna ingin menjawab panggilan dan menginisialisasi panggilan dengan pengguna lain.
- Beri tahu Sistem dan Jaringan Komunikasi ketika pengguna ingin mengakhiri panggilan saat ini.
Bagian berikut akan melihat secara rinci bagaimana aplikasi dapat menggunakan CallKit untuk menangani alur kerja panggilan masuk, sekali lagi menggunakan aplikasi MonkeyCall VOIP sebagai contoh.
Memberi tahu pengguna tentang panggilan masuk
Saat pengguna jarak jauh telah memulai percakapan VOIP dengan pengguna lokal, hal berikut ini terjadi:
- Aplikasi ini mendapatkan pemberitahuan dari Communications Network-nya bahwa ada panggilan VOIP masuk.
- Aplikasi ini menggunakan
CXProvider
untuk mengirimCXCallUpdate
ke Sistem yang menginformasikan panggilan. - Sistem menerbitkan panggilan ke UI Sistem, Layanan Sistem, dan aplikasi VOIP lainnya menggunakan CallKit.
Misalnya, dalam 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);
}
});
}
Kode ini membuat instans baru CXCallUpdate
dan melampirkan handel ke instans yang akan mengidentifikasi pemanggil. Selanjutnya, menggunakan ReportNewIncomingCall
metode CXProvider
kelas untuk menginformasikan sistem panggilan. Jika berhasil, panggilan ditambahkan ke kumpulan panggilan aktif aplikasi, jika tidak, kesalahan perlu dilaporkan kepada pengguna.
Pengguna menjawab panggilan masuk
Jika pengguna ingin menjawab panggilan VOIP masuk, hal berikut terjadi:
- UI Sistem memberi tahu Sistem bahwa pengguna ingin menjawab panggilan VOIP.
- Sistem mengirimkan
CXAnswerCallAction
ke aplikasiCXProvider
yang menginformasikannya tentang Niat Jawaban. - Aplikasi ini menginformasikan Jaringan Komunikasinya bahwa pengguna menjawab panggilan dan panggilan VOIP berjalan seperti biasa.
Misalnya, dalam 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 ();
}
});
}
Kode ini pertama-tama mencari panggilan yang diberikan dalam daftar panggilan aktifnya. Jika panggilan tidak dapat ditemukan, sistem akan diberi tahu dan metode keluar. Jika ditemukan, AnswerCall
metode kelas dipanggil ActiveCall
untuk memulai panggilan dan Sistem adalah informasi jika berhasil atau gagal.
Pengguna mengakhiri panggilan masuk
Jika pengguna ingin menghentikan panggilan dari dalam UI aplikasi, hal berikut terjadi:
- Aplikasi membuat
CXEndCallAction
yang dibundel ke dalamCXTransaction
yang dikirim ke Sistem untuk memberi tahunya bahwa panggilan berakhir. - Sistem memverifikasi Niat Akhiri Panggilan dan mengirim
CXEndCallAction
kembali ke aplikasi melaluiCXProvider
. - Aplikasi kemudian menginformasikan Jaringan Komunikasinya bahwa panggilan berakhir.
Misalnya, dalam 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 ();
}
});
}
Kode ini pertama-tama mencari panggilan yang diberikan dalam daftar panggilan aktifnya. Jika panggilan tidak dapat ditemukan, sistem akan diberi tahu dan metode keluar. Jika ditemukan, EndCall
metode kelas dipanggil ActiveCall
untuk mengakhiri panggilan dan Sistem adalah informasi jika berhasil atau gagal. Jika berhasil, panggilan akan dihapus dari kumpulan panggilan aktif.
Mengelola beberapa panggilan
Sebagian besar aplikasi VOIP dapat menangani beberapa panggilan sekaligus. Misalnya, jika saat ini ada panggilan VOIP aktif dan aplikasi mendapatkan pemberitahuan bahwa ada panggilan masuk baru, pengguna dapat menjeda atau menutup panggilan pertama untuk menjawab panggilan kedua.
Dalam situasi yang diberikan di atas, Sistem akan mengirim CXTransaction
ke aplikasi yang akan menyertakan daftar beberapa tindakan (seperti CXEndCallAction
dan CXAnswerCallAction
). Semua tindakan ini perlu dipenuhi secara individual, sehingga Sistem dapat memperbarui UI dengan tepat.
Menangani panggilan keluar
Jika pengguna mengetuk entri dari daftar Terbaru (di aplikasi Telepon), misalnya, yang berasal dari panggilan milik aplikasi, itu akan dikirimi Niat Mulai Panggilan oleh sistem:
- Aplikasi akan membuat Tindakan Mulai Panggilan berdasarkan Niat Mulai Panggilan yang diterimanya dari Sistem.
- Aplikasi akan menggunakan
CXCallController
untuk meminta Tindakan Mulai Panggilan dari sistem. - Jika Sistem menerima Tindakan, tindakan tersebut akan dikembalikan ke aplikasi melalui
XCProvider
delegasi. - Aplikasi memulai panggilan keluar dengan Communication Network-nya.
Untuk informasi selengkapnya tentang Niat, silakan lihat dokumentasi Ekstensi UI Niat dan Niat kami.
Siklus hidup panggilan keluar
Saat bekerja dengan CallKit dan panggilan keluar, aplikasi harus menginformasikan Sistem peristiwa siklus hidup berikut:
- Memulai - Menginformasikan sistem bahwa panggilan keluar akan dimulai.
- Dimulai - Beri tahu sistem bahwa panggilan keluar telah dimulai.
- Koneksi - Beri tahu sistem bahwa panggilan keluar tersambung.
- Koneksi - Beri tahu panggilan keluar telah terhubung dan kedua belah pihak dapat berbicara sekarang.
Misalnya, kode berikut akan memulai panggilan keluar:
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);
}
Ini membuat CXHandle
dan menggunakannya untuk mengonfigurasi CXStartCallAction
yang dibundel ke dalam CXTransaction
yang dikirim ke Sistem menggunakan RequestTransaction
metode CXCallController
kelas . Dengan memanggil RequestTransaction
metode , Sistem dapat melakukan panggilan yang ada saat ditangguhkan, apa pun sumbernya (aplikasi Telepon, FaceTime, VOIP, dll.), sebelum panggilan baru dimulai.
Permintaan untuk memulai panggilan VOIP keluar dapat berasal dari beberapa sumber yang berbeda, seperti Siri, entri pada kartu Kontak (di aplikasi Kontak) atau dari daftar Terbaru (di aplikasi Telepon). Dalam situasi ini, aplikasi akan dikirimi Niat Mulai Panggilan di dalam dan NSUserActivity
AppDelegate perlu menanganinya:
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
var handle = StartCallRequest.CallHandleFromActivity (userActivity);
// Found?
if (handle == null) {
// No, report to system
Console.WriteLine ("Unable to get call handle from User Activity: {0}", userActivity);
return false;
} else {
// Yes, start call and inform system
CallManager.StartCall (handle);
return true;
}
}
CallHandleFromActivity
Di sini metode kelas StartCallRequest
pembantu sedang digunakan untuk mendapatkan pegangan kepada orang yang dipanggil (lihat Kelas StartCallRequest di atas).
Metode PerformStartCallAction
Kelas ProviderDelegate digunakan untuk akhirnya memulai panggilan keluar aktual dan menginformasikan Sistem siklus hidupnya:
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 ();
}
});
}
Ini membuat instans ActiveCall
kelas (untuk menyimpan informasi tentang panggilan yang sedang berlangsung) dan mengisi dengan orang yang dipanggil. Peristiwa StartingConnectionChanged
dan ConnectedChanged
digunakan untuk memantau dan melaporkan siklus hidup panggilan keluar. Panggilan dimulai dan Sistem menginformasikan bahwa Tindakan telah terpenuhi.
Mengakhiri panggilan keluar
Ketika pengguna telah selesai dengan panggilan keluar dan ingin mengakhirinya, kode berikut dapat digunakan:
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);
}
Jika membuat CXEndCallAction
dengan UUID panggilan ke akhir, bundelkan dalam CXTransaction
yang dikirim ke Sistem menggunakan RequestTransaction
metode CXCallController
kelas .
Detail CallKit tambahan
Bagian ini akan membahas beberapa detail tambahan yang perlu dipertimbangkan pengembang saat bekerja dengan CallKit seperti:
- Konfigurasi Penyedia
- Kesalahan Tindakan
- Pembatasan Sistem
- VOIP Audio
Konfigurasi penyedia layanan
Konfigurasi penyedia memungkinkan aplikasi VOIP iOS 10 menyesuaikan pengalaman pengguna (di dalam UI In-Call asli) saat bekerja dengan CallKit.
Aplikasi dapat membuat jenis kustomisasi berikut:
- Tampilkan nama yang dilokalkan.
- Aktifkan dukungan panggilan video.
- Sesuaikan tombol pada UI Dalam Panggilan dengan menyajikan ikon gambar templatnya sendiri. Interaksi pengguna dengan tombol kustom dikirim langsung ke aplikasi untuk diproses.
Kesalahan tindakan
Aplikasi VOIP iOS 10 yang menggunakan CallKit perlu menangani Tindakan yang gagal dengan anggun dan membuat pengguna mendapatkan informasi tentang status Tindakan setiap saat.
Pertimbangkan contoh berikut:
- Aplikasi ini telah menerima Tindakan Mulai Panggilan dan telah memulai proses inisialisasi panggilan VOIP baru dengan Jaringan Komunikasinya.
- Karena kemampuan komunikasi jaringan terbatas atau tidak ada, koneksi ini gagal.
- Aplikasi harus mengirim pesan Gagal kembali ke Tindakan Mulai Panggilan (
Action.Fail()
) untuk menginformasikan Sistem kegagalan. - Ini memungkinkan Sistem untuk memberi tahu pengguna tentang status panggilan. Misalnya, untuk menampilkan UI Kegagalan Panggilan.
Selain itu, aplikasi VOIP iOS 10 harus merespons Kesalahan Waktu Habis yang dapat terjadi ketika Tindakan yang diharapkan tidak dapat diproses dalam waktu tertentu. Setiap Jenis Tindakan yang disediakan oleh CallKit memiliki nilai Batas Waktu maksimum yang terkait dengannya. Nilai Timeout ini memastikan bahwa Setiap Tindakan CallKit yang diminta oleh pengguna ditangani dengan cara yang responsif, sehingga menjaga cairan OS dan responsif juga.
Ada beberapa metode pada Delegasi Penyedia (CXProviderDelegate
) yang harus ditimpa untuk menangani situasi Batas Waktu ini dengan baik juga.
Pembatasan sistem
Berdasarkan status perangkat iOS saat ini yang menjalankan aplikasi VOIP iOS 10, pembatasan sistem tertentu dapat diberlakukan.
Misalnya, panggilan VOIP masuk dapat dibatasi oleh Sistem jika:
- Orang yang memanggil ada di Daftar Pemanggil yang Diblokir pengguna.
- Perangkat iOS pengguna berada dalam mode Jangan Diganggu.
Jika panggilan VOIP dibatasi oleh salah satu situasi ini, gunakan kode berikut untuk menanganinya:
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
}
}
});
}
}
Audio VOIP
CallKit memberikan beberapa manfaat untuk menangani sumber daya audio yang akan diperlukan aplikasi VOIP iOS 10 selama panggilan VOIP langsung. Salah satu manfaat terbesar adalah sesi audio aplikasi akan memiliki prioritas yang ditingkatkan saat berjalan di iOS 10. Ini adalah tingkat prioritas yang sama dengan aplikasi Telepon dan FaceTime bawaan dan tingkat prioritas yang ditingkatkan ini akan mencegah aplikasi lain yang berjalan mengganggu sesi audio aplikasi VOIP.
Selain itu, CallKit memiliki akses ke petunjuk perutean audio lain yang dapat meningkatkan performa dan dengan cerdas merutekan audio VOIP ke perangkat output tertentu selama panggilan langsung berdasarkan preferensi pengguna dan status perangkat. Misalnya, berdasarkan perangkat yang terpasang seperti headphone bluetooth, koneksi CarPlay langsung, atau pengaturan Aksesibilitas.
Selama siklus hidup panggilan VOIP yang khas menggunakan CallKit, aplikasi harus mengonfigurasi Aliran Audio yang akan disediakan CallKit. Lihat contoh berikut:
- Tindakan Mulai Panggilan diterima oleh aplikasi untuk menjawab panggilan masuk.
- Sebelum Tindakan ini dipenuhi oleh aplikasi, tindakan ini menyediakan konfigurasi yang akan diperlukan untuknya
AVAudioSession
. - Aplikasi ini memberi tahu Sistem bahwa Tindakan telah terpenuhi.
- Sebelum panggilan tersambung, CallKit menyediakan prioritas
AVAudioSession
tinggi yang cocok dengan konfigurasi yang diminta aplikasi. Aplikasi akan diberi tahu melaluiDidActivateAudioSession
metode .CXProviderDelegate
Bekerja dengan ekstensi direktori panggilan
Saat bekerja dengan CallKit, Ekstensi Direktori Panggilan menyediakan cara untuk menambahkan nomor panggilan yang diblokir dan mengidentifikasi nomor yang khusus untuk aplikasi VOIP tertentu untuk kontak di aplikasi Kontak di perangkat iOS.
Menerapkan ekstensi direktori Panggilan
Untuk menerapkan Ekstensi Direktori Panggilan di aplikasi Xamarin.iOS, lakukan hal berikut:
Buka solusi aplikasi di Visual Studio untuk Mac.
Klik kanan Pada Nama Solusi di Penjelajah Solusi dan pilih Tambahkan>Proyek Baru.
Pilih Ekstensi iOS>>Panggil Ekstensi Direktori dan klik tombol Berikutnya:
Masukkan Nama untuk ekstensi dan klik tombol Berikutnya :
Sesuaikan Nama Proyek dan/atau Nama Solusi jika diperlukan dan klik tombol Buat :
Ini akan menambahkan CallDirectoryHandler.cs
kelas ke proyek yang terlihat seperti berikut ini:
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
}
}
Metode BeginRequest
dalam Penanganan Direktori Panggilan perlu dimodifikasi untuk menyediakan fungsionalitas yang diperlukan. Dalam kasus sampel di atas, ia mencoba mengatur daftar nomor yang diblokir dan tersedia dalam database kontak aplikasi VOIP. Jika salah satu permintaan gagal karena alasan apa pun, buat NSError
untuk menjelaskan kegagalan dan meneruskannya metode CancelRequest
CXCallDirectoryExtensionContext
kelas.
Untuk mengatur angka yang diblokir, gunakan AddBlockingEntry
metode CXCallDirectoryExtensionContext
kelas . Angka yang disediakan untuk metode harus dalam urutan naik numerik. Untuk performa optimal dan penggunaan memori ketika ada banyak nomor telepon, pertimbangkan untuk hanya memuat subset angka pada waktu tertentu dan menggunakan kumpulan penghapusan otomatis untuk melepaskan objek yang dialokasikan selama setiap batch nomor yang dimuat.
Untuk menginformasikan ke Aplikasi kontak dari nomor kontak yang diketahui oleh aplikasi VOIP, gunakan AddIdentificationEntry
metode CXCallDirectoryExtensionContext
kelas dan berikan nomor dan label identifikasi. Sekali lagi, angka yang disediakan untuk metode harus dalam urutan naik numerik. Untuk performa optimal dan penggunaan memori ketika ada banyak nomor telepon, pertimbangkan untuk hanya memuat subset angka pada waktu tertentu dan menggunakan kumpulan penghapusan otomatis untuk melepaskan objek yang dialokasikan selama setiap batch nomor yang dimuat.
Ringkasan
Artikel ini telah membahas CallKit API baru yang dirilis Apple di iOS 10 dan cara menerapkannya di aplikasi VOIP Xamarin.iOS. Ini telah menunjukkan bagaimana CallKit memungkinkan aplikasi untuk diintegrasikan ke dalam Sistem iOS, bagaimana aplikasi tersebut menyediakan paritas fitur dengan aplikasi bawaan (seperti Telepon) dan bagaimana aplikasi meningkatkan visibilitas aplikasi di seluruh iOS di lokasi seperti Layar Kunci dan Beranda, melalui interaksi Siri dan melalui aplikasi Kontak.