/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
// swiftlint:disable cyclomatic_complexity
// swiftlint:disable line_length
// swiftlint:disable type_body_length
import Foundation
import linphonesw
import UserNotifications
import os
import CallKit
import AVFoundation
import SwiftUI
class CallAppData: NSObject {
var batteryWarningShown = false
var videoRequested = false /*set when user has requested for video*/
var isConference = false
}
class TelecomManager: ObservableObject {
static let shared = TelecomManager()
static var uuidReplacedCall: String?
let providerDelegate: ProviderDelegate // to support callkit
let callController: CXCallController // to support callkit
@Published var callInProgress: Bool = false
@Published var callDisplayed: Bool = true
@Published var callStarted: Bool = false
@Published var outgoingCallStarted: Bool = false
@Published var remoteConfVideo: Bool = false
@Published var isRecordingByRemote: Bool = false
@Published var isPausedByRemote: Bool = false
@Published var refreshCallViewModel: Bool = false
@Published var remainingCall: Bool = false
@Published var callConnected: Bool = false
@Published var meetingWaitingRoomDisplayed: Bool = false
@Published var meetingWaitingRoomSelected: Address?
@Published var meetingWaitingRoomName: String = ""
@Published var participantsInvited: Bool = false
var actionToFulFill: CXCallAction?
var callkitAudioSessionActivated: Bool?
var nextCallIsTransfer: Bool = false
var speakerBeforePause: Bool = false
var endCallkit: Bool = false
var endCallKitReplacedCall: Bool = true
var backgroundContextCall: Call?
var backgroundContextCameraIsEnabled: Bool = false
var referedFromCall: String?
var referedToCall: String?
var actionsToPerformOnceWhenCoreIsOn: [(() -> Void)] = []
private init() {
providerDelegate = ProviderDelegate()
callController = CXCallController()
}
func addAllToLocalConference(core: Core) {
// TODO
}
static func getAppData(sCall: Call) -> CallAppData? {
if sCall.userData == nil {
return nil
}
return Unmanaged.fromOpaque(sCall.userData!).takeUnretainedValue()
}
static func setAppData(sCall: Call, appData: CallAppData?) {
if sCall.userData != nil {
Unmanaged.fromOpaque(sCall.userData!).release()
}
if appData == nil {
sCall.userData = nil
} else {
sCall.userData = UnsafeMutableRawPointer(Unmanaged.passRetained(appData!).toOpaque())
}
}
func startCallCallKit(core: Core, addr: Address?, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
if addr == nil {
Log.info("Can not start a call with null address!")
return
}
if TelecomManager.callKitEnabled(core: core) {// && !nextCallIsTransfer != true {
let uuid = UUID()
let name = addr?.asStringUriOnly() ?? "Unknown"
let handle = CXHandle(type: .generic, value: addr?.asStringUriOnly() ?? "")
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction)
let callInfo = CallInfo.newOutgoingCallInfo(addr: addr!, isSas: isSas, displayName: name, isVideo: isVideo, isConference: isConference)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "")
setHeldOtherCalls(core: core, exceptCallid: "")
requestTransaction(transaction, action: "startCall")
DispatchQueue.main.async {
withAnimation {
self.callDisplayed = true
}
}
} else {
try doCall(core: core, addr: addr!, isSas: isSas, isVideo: isVideo, isConference: isConference)
}
}
func setHeldOtherCalls(core: Core, exceptCallid: String) {
for call in core.calls {
if call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote {
setHeld(call: call, hold: true)
} else if call.callLog?.callId == exceptCallid && (call.state == .Paused || call.state == .Pausing || call.state == .PausedByRemote) {
setHeld(call: call, hold: true)
}
}
}
func setHeld(call: Call, hold: Bool) {
#if targetEnvironment(simulator)
if hold {
try?call.pause()
} else {
try?call.resume()
}
#else
let callid = call.callLog?.callId ?? ""
let uuid = providerDelegate.uuids["\(callid)"]
if uuid == nil {
Log.error("Can not find correspondant call to set held.")
return
}
let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold)
let transaction = CXTransaction(action: setHeldAction)
requestTransaction(transaction, action: "setHeld")
#endif
}
func startCall(core: Core, addr: String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) {
do {
let address = try Factory.Instance.createAddress(addr: addr)
try startCallCallKit(core: core, addr: address, isSas: isSas, isVideo: isVideo, isConference: isConference)
} catch {
Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ")
}
}
func doCallOrJoinConf(address: Address, isVideo: Bool = false, isConference: Bool = false) {
if address.asStringUriOnly().hasPrefix("sip:conference-focus@sip.linphone.org") {
do {
let meetingAddress = try Factory.Instance.createAddress(addr: address.asStringUriOnly())
DispatchQueue.main.async {
withAnimation {
self.meetingWaitingRoomDisplayed = true
self.meetingWaitingRoomSelected = meetingAddress
}
}
} catch {}
} else {
doCallWithCore(
addr: address, isVideo: isVideo, isConference: isConference
)
}
}
func doCallWithCore(addr: Address, isVideo: Bool, isConference: Bool) {
CoreContext.shared.doOnCoreQueue { core in
do {
try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: isVideo, isConference: isConference)
} catch {
Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ")
}
}
}
private func makeRecordFilePath() -> String {
var filePath = "recording_"
let now = Date()
let dateFormat = DateFormatter()
dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
let date = dateFormat.string(from: now)
filePath = filePath.appending("\(date).mkv")
let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
let writablePath = paths[0]
return writablePath.appending("/\(filePath)")
}
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
// let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try core.createCallParams(call: nil)
/*
if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true
}
if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!)
}
if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
}
*/
if nextCallIsTransfer {
let call = core.currentCall
try call?.transferTo(referTo: addr)
nextCallIsTransfer = false
} else {
// We set the record file name here because we can't do it after the call is started.
// let writablePath = AppManager.recordingFilePathFromCall(address: addr.username! )
// Log.directLog(BCTBX_LOG_DEBUG, text: "record file path: \(writablePath)")
// lcallParams.recordFile = writablePath
lcallParams.recordFile = makeRecordFilePath()
if isSas {
lcallParams.mediaEncryption = .ZRTP
}
if isConference {
lcallParams.videoEnabled = true
lcallParams.videoDirection = isVideo && core.videoPreviewEnabled ? MediaDirection.SendRecv : MediaDirection.RecvOnly
/* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
lcallParams.videoEnabled = true
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
} else {
lcallParams.videoEnabled = false
}*/
} else {
if isVideo {
lcallParams.videoEnabled = true
lcallParams.videoDirection = MediaDirection.SendRecv
} else {
lcallParams.videoEnabled = false
}
}
if let call = core.inviteAddressWithParams(addr: addr, params: lcallParams) {
// The LinphoneCallAppData object should be set on call creation with callback
// - (void)onCall:StateChanged:withMessage:. If not, we are in big trouble and expect it to crash
// We are NOT responsible for creating the AppData.
if let data = TelecomManager.getAppData(sCall: call) {
data.isConference = isConference
data.videoRequested = lcallParams.videoEnabled
TelecomManager.setAppData(sCall: call, appData: data)
} else {
Log.error("New call instanciated but app data was not set. Expect it to crash.")
/* will be used later to notify user if video was not activated because of the linphone core*/
}
}
DispatchQueue.main.async {
self.outgoingCallStarted = true
self.callStarted = true
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
self.callDisplayed = true
}
}
}
}
}
func acceptCall(core: Core, call: Call, hasVideo: Bool) {
do {
let callParams = try core.createCallParams(call: call)
callParams.recordFile = makeRecordFilePath()
callParams.videoEnabled = hasVideo
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g)
if (low_bandwidth) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
}
callParams.lowBandwidthEnabled = low_bandwidth
}*/
// We set the record file name here because we can't do it after the call is started.
// let address = call.callLog?.fromAddress
// let writablePath = AppManager.recordingFilePathFromCall(address: address?.username ?? "")
// Log.directLog(BCTBX_LOG_MESSAGE, text: "Record file path: \(String(describing: writablePath))")
// callParams.recordFile = writablePath
/*
if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.")
chatView.stopVoiceRecording()
}*/
if call.callLog?.wasConference() == true {
// Prevent incoming group call to start in audio only layout
// Do the same as the conference waiting room
callParams.videoEnabled = true
callParams.videoDirection = core.videoActivationPolicy?.automaticallyInitiate == true ? .SendRecv : .RecvOnly
Log.info("[Context] Enabling video on call params to prevent audio-only layout when answering")
}
try call.acceptWithParams(params: callParams)
DispatchQueue.main.async {
self.callStarted = true
if self.callDisplayed {
self.callDisplayed = core.calls.count <= 1
}
}
if core.calls.count > 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.callDisplayed = true
}
}
} catch {
Log.error("accept call failed \(error)")
}
}
func terminateCall(call: Call) {
CoreContext.shared.doOnCoreQueue { _ in
do {
try call.terminate()
Log.info("Call terminated")
} catch {
Log.error("Failed to terminate call failed because \(error)")
}
}
}
func displayIncomingCall(call: Call?, handle: String, hasVideo: Bool, callId: String, displayName: String) {
let uuid = UUID()
let callInfo = CallInfo.newIncomingCallInfo(callId: callId)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: callId)
providerDelegate.reportIncomingCall(call: call, uuid: uuid, handle: handle, hasVideo: hasVideo, displayName: displayName)
}
func incomingDisplayName(call: Call, completion: @escaping (String) -> Void) {
CoreContext.shared.doOnCoreQueue { _ in
ContactsManager.shared.getFriendWithAddressInCoreQueue(address: call.remoteAddress!) { friendResult in
if call.remoteAddress != nil {
if call.callLog?.wasConference() != true {
if let addressFriend = friendResult {
completion(addressFriend.name!)
} else if let remoteAddress = call.remoteAddress {
if remoteAddress.displayName != nil {
completion(remoteAddress.displayName!)
} else if remoteAddress.username != nil {
completion(remoteAddress.username!)
} else {
completion(String(remoteAddress.asStringUriOnly().dropFirst(4)))
}
}
} else {
completion(call.callLog?.conferenceInfo?.subject ?? "Error Conference Name")
}
} else {
completion("IncomingDisplayName")
}
}
}
}
static func callKitEnabled(core: Core) -> Bool {
#if !targetEnvironment(simulator)
return core.callkitEnabled
#else
return false
#endif
}
func requestTransaction(_ transaction: CXTransaction, action: String) {
callController.request(transaction) { error in
if let error = error {
Log.error("CallKit: Requested transaction \(action) failed because: \(error)")
} else {
Log.info("CallKit: Requested transaction \(action) successfully")
}
}
}
func onAccountRegistrationStateChanged(core: Core, account: Account, state: RegistrationState, message: String) {
if core.accountList.count == 1 && (state == .Failed || state == .Cleared) {
// terminate callkit immediately when registration failed or cleared, supporting single account configuration
for call in providerDelegate.uuids {
let callId = providerDelegate.callInfos[call.value]?.callId
if callId != nil {
let call = core.getCallByCallid(callId: callId!)
if call != nil && call?.state != .PushIncomingReceived {
// sometimes (for example) due to network, registration failed, in this case, keep the call
continue
}
}
providerDelegate.endCall(uuid: call.value)
}
endCallkit = true
} else {
endCallkit = false
}
}
func updateRemoteConfVideo(remConfVideoEnabled: Bool) {
if self.remoteConfVideo != remConfVideoEnabled {
DispatchQueue.main.async {
self.remoteConfVideo.toggle()
Log.info("[Call] Remote video is \(remConfVideoEnabled ? "activated" : "not activated")")
}
}
}
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
let callLog = call.callLog
let callId = callLog?.callId ?? ""
if !callInProgress && participantsInvited {
if let remoteAddress = call.remoteAddress {
let uuid = UUID()
let name = remoteAddress.asStringUriOnly()
let handle = CXHandle(type: .generic, value: remoteAddress.asStringUriOnly())
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction)
let callInfo = CallInfo.newOutgoingCallInfo(addr: remoteAddress, isSas: false, displayName: name, isVideo: true, isConference: true)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: callId)
setHeldOtherCalls(core: core, exceptCallid: callId)
requestTransaction(transaction, action: "startCall")
DispatchQueue.main.async {
self.participantsInvited = false
withAnimation {
self.callDisplayed = true
}
}
}
}
if cstate == .PushIncomingReceived {
Log.info("PushIncomingReceived in core delegate, display callkit call")
TelecomManager.shared.displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling")
} else {
// let oldRemoteConfVideo = self.remoteConfVideo
if call.conference != nil {
if call.conference!.activeSpeakerParticipantDevice != nil {
let direction = call.conference?.activeSpeakerParticipantDevice!.getStreamCapability(streamType: StreamType.Video)
updateRemoteConfVideo(remConfVideoEnabled: direction == .SendRecv || direction == .SendOnly)
} else if call.conference!.participantList.first != nil && call.conference!.participantDeviceList.first != nil
&& call.conference!.participantList.first?.address != nil
&& call.conference!.participantList.first!.address!.clone()!.equal(address2: (call.conference!.me?.address)!) {
let direction = call.conference!.participantDeviceList.first!.getStreamCapability(streamType: StreamType.Video)
updateRemoteConfVideo(remConfVideoEnabled: direction == .SendRecv || direction == .SendOnly)
} else if call.conference!.participantList.last != nil && call.conference!.participantDeviceList.last != nil
&& call.conference!.participantList.last?.address != nil {
let direction = call.conference!.participantDeviceList.last!.getStreamCapability(streamType: StreamType.Video)
updateRemoteConfVideo(remConfVideoEnabled: direction == .SendRecv || direction == .SendOnly)
} else {
updateRemoteConfVideo(remConfVideoEnabled: false)
}
} else {
var remConfVideoEnabled = false
if call.currentParams != nil {
remConfVideoEnabled = call.currentParams!.videoEnabled && call.currentParams!.videoDirection == .SendRecv || call.currentParams!.videoDirection == .RecvOnly
}
updateRemoteConfVideo(remConfVideoEnabled: remConfVideoEnabled)
}
if self.remoteConfVideo {
Log.info("[Call] Remote video is activated")
}
let isRecordingByRemoteTmp = call.remoteParams?.isRecording ?? false
if isRecordingByRemoteTmp && ToastViewModel.shared.toastMessage.isEmpty {
var displayName = ""
let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!)
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
displayName = friend!.address!.displayName!
} else {
if call.remoteAddress!.displayName != nil {
displayName = call.remoteAddress!.displayName!
} else if call.remoteAddress!.username != nil {
displayName = call.remoteAddress!.username!
} else {
displayName = String(call.remoteAddress!.asStringUriOnly().dropFirst(4))
}
}
DispatchQueue.main.async {
self.isRecordingByRemote = isRecordingByRemoteTmp
ToastViewModel.shared.toastMessage = "\(displayName) is recording"
ToastViewModel.shared.displayToast = true
}
Log.info("[Call] Call is recording by \(call.remoteAddress!.asStringUriOnly())")
}
if !isRecordingByRemoteTmp && ToastViewModel.shared.toastMessage.contains("is recording") {
DispatchQueue.main.async {
self.isRecordingByRemote = isRecordingByRemoteTmp
ToastViewModel.shared.toastMessage = ""
ToastViewModel.shared.displayToast = false
}
Log.info("[Call] Recording is stopped by \(call.remoteAddress!.asStringUriOnly())")
}
if cstate == Call.State.PausedByRemote {
DispatchQueue.main.async {
self.isPausedByRemote = true
}
} else {
DispatchQueue.main.async {
self.isPausedByRemote = false
}
}
if cstate == Call.State.Connected {
DispatchQueue.main.async {
self.callConnected = true
self.meetingWaitingRoomSelected = nil
self.meetingWaitingRoomDisplayed = false
}
}
if call.userData == nil {
let appData = CallAppData()
TelecomManager.setAppData(sCall: call, appData: appData)
}
switch cstate {
case .IncomingReceived:
let addr = call.remoteAddress
incomingDisplayName(call: call) { displayNameResult in
let displayName = displayNameResult
#if targetEnvironment(simulator)
DispatchQueue.main.async {
self.outgoingCallStarted = false
self.callStarted = false
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
self.callDisplayed = true
}
}
}
#endif
if TelecomManager.callKitEnabled(core: core) {
let uuid = self.providerDelegate.uuids["\(callId)"]
TelecomManager.uuidReplacedCall = callId
if uuid != nil {
// Tha app is now registered, updated the call already existed.
self.providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: self.remoteConfVideo, displayName: displayName)
} else {
let videoEnabled = call.remoteParams?.videoEnabled ?? false
let isConference = call.callLog?.wasConference() ?? false
let videoDir = call.remoteParams?.videoDirection != MediaDirection.Inactive
self.displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: videoEnabled && videoDir && !isConference, callId: callId, displayName: displayName)
}
}
}
case .StreamsRunning:
if TelecomManager.callKitEnabled(core: core) {
DispatchQueue.main.async {
self.outgoingCallStarted = false
}
let uuid = providerDelegate.uuids["\(callId)"]
if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!]
if callInfo != nil && callInfo!.isOutgoing && !callInfo!.connected {
Log.info("CallKit: outgoing call connected with uuid \(uuid!) and callId \(callId)")
providerDelegate.reportOutgoingCallConnected(uuid: uuid!)
callInfo!.connected = true
providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
}
}
}
actionToFulFill?.fulfill()
actionToFulFill = nil
case .Paused:
actionToFulFill?.fulfill()
actionToFulFill = nil
case .OutgoingInit,
.OutgoingProgress,
.OutgoingRinging,
.OutgoingEarlyMedia:
if TelecomManager.callKitEnabled(core: core) {
let uuid = providerDelegate.uuids[""]
if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!]
callInfo!.callId = callId
providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
providerDelegate.uuids.removeValue(forKey: "")
providerDelegate.uuids.updateValue(uuid!, forKey: callId)
Log.info("CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId)")
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else {
referedToCall = callId
}
}
case .End,
.Error:
UIDevice.current.isProximityMonitoringEnabled = false
if core.callsNb == 0 {
core.outputAudioDevice = core.defaultOutputAudioDevice
}
// if core.callsNb == 0 {
self.incomingDisplayName(call: call) { displayNameResult in
var displayName = "Unknown"
if call.dir == .Incoming {
displayName = displayNameResult
} else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
displayName = "TODOContactName"
}
DispatchQueue.main.async {
if core.callsNb == 0 {
do {
try core.setVideodevice(newValue: "AV Capture: com.apple.avfoundation.avcapturedevice.built-in_video:1")
} catch _ {
}
withAnimation {
self.outgoingCallStarted = false
self.callInProgress = false
self.callDisplayed = false
self.callStarted = false
self.callConnected = false
}
} else {
if core.calls.last != nil {
self.setHeld(call: core.calls.last!, hold: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.remainingCall = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.remainingCall = false
}
}
}
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("notification_missed_call_title", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error: Error?) in
if error != nil {
Log.info("Error while adding notification request : \(error!.localizedDescription)")
}
}
}
}
}
// }
if TelecomManager.callKitEnabled(core: core) {
var uuid = providerDelegate.uuids["\(callId)"]
if callId == referedToCall {
// refered call ended before connecting
Log.info("Callkit: end refered to call: \(String(describing: referedToCall))")
referedFromCall = nil
referedToCall = nil
}
if uuid == nil {
// the call not yet connected
uuid = providerDelegate.uuids[""]
}
if uuid != nil {
if callId == referedFromCall {
Log.info("Callkit: end refered from call: \(String(describing: referedFromCall))")
referedFromCall = nil
let callInfo = providerDelegate.callInfos[uuid!]
callInfo!.callId = referedToCall ?? ""
providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
providerDelegate.uuids.removeValue(forKey: callId)
providerDelegate.uuids.updateValue(uuid!, forKey: callInfo!.callId)
referedToCall = nil
break
}
if endCallKitReplacedCall {
let transaction = CXTransaction(action: CXEndCallAction(call: uuid!))
requestTransaction(transaction, action: "endCall")
} else {
endCallKitReplacedCall = true
}
}
}
case .Released:
TelecomManager.setAppData(sCall: call, appData: nil)
case .Referred:
referedFromCall = call.callLog?.callId
default:
break
}
}
// post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [
AnyHashable("call"): NSValue.init(pointer: UnsafeRawPointer(call.getCobject)),
AnyHashable("state"): NSNumber(value: cstate.rawValue),
AnyHashable("message"): message
])
}
}
// swiftlint:enable type_body_length
// swiftlint:enable cyclomatic_complexity
// swiftlint:enable line_length